Roll WebGPU CTS; disable worker tests; remove results json Changes in this CL: - Roll external/wpt copy of CTS build to master branch - Roll wpt_internal copy of CTS build to glsl-dependent branch - Update expectations (with some broad skips) - Remove running EVERY test with both worker=0 and worker=1 - Run a few operation tests explicitly with worker=1 - Remove 'results' textarea from cts.html (following upstream) Bug: 1083478, 1069953 Bug: 1069302 Cq-Do-Not-Cancel-Tryjobs: true Change-Id: I4b9b9f87d080b40577e7f4aeaf5f4a250b31e6e8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2200094 Commit-Queue: Kai Ninomiya <kainino@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org> Cr-Commit-Position: refs/heads/master@{#770651} diff --git a/webgpu/common/constants.js b/webgpu/common/constants.js index 9d3dd1a..69496b1 100644 --- a/webgpu/common/constants.js +++ b/webgpu/common/constants.js
@@ -3,6 +3,7 @@ **/ // https://github.com/gpuweb/gpuweb/blob/0a48816412b5d08a5fb8b89005e019165a1a2c63/spec/index.bs +// tslint:disable:variable-name // String enums export let ExtensionName; @@ -180,7 +181,7 @@ TextureFormat["BGRA8Unorm"] = "bgra8unorm"; TextureFormat["BGRA8UnormSRGB"] = "bgra8unorm-srgb"; TextureFormat["RGB10A2Unorm"] = "rgb10a2unorm"; - TextureFormat["RGB11B10Float"] = "rg11b10float"; + TextureFormat["RG11B10Float"] = "rg11b10float"; TextureFormat["RG32Uint"] = "rg32uint"; TextureFormat["RG32Sint"] = "rg32sint"; TextureFormat["RG32Float"] = "rg32float";
diff --git a/webgpu/common/framework/file_loader.js b/webgpu/common/framework/file_loader.js new file mode 100644 index 0000000..1b77a4e --- /dev/null +++ b/webgpu/common/framework/file_loader.js
@@ -0,0 +1,36 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { parseQuery } from './query/parseQuery.js'; +import { loadTreeForQuery } from './tree.js'; // A listing file, e.g. either of: +// - `src/webgpu/listing.ts` (which is dynamically computed, has a Promise<TestSuiteListing>) +// - `out/webgpu/listing.js` (which is pre-baked, has a TestSuiteListing) + +// Base class for DefaultTestFileLoader and FakeTestFileLoader. +export class TestFileLoader { + importSpecFile(suite, path) { + return this.import(`${suite}/${path.join('/')}.spec.js`); + } + + async loadTree(query, subqueriesToExpand = []) { + return loadTreeForQuery(this, parseQuery(query), subqueriesToExpand.map(q => parseQuery(q))); + } + + async loadTests(query) { + const tree = await this.loadTree(query); + return tree.iterateLeaves(); + } + +} +export class DefaultTestFileLoader extends TestFileLoader { + async listing(suite) { + return (await import(`../../${suite}/listing.js`)).listing; + } + + import(path) { + return import(`../../${path}`); + } + +} +//# sourceMappingURL=file_loader.js.map \ No newline at end of file diff --git a/webgpu/common/framework/fixture.js b/webgpu/common/framework/fixture.js index 55f8ec4..ef23f06 100644 --- a/webgpu/common/framework/fixture.js +++ b/webgpu/common/framework/fixture.js
@@ -45,7 +45,7 @@ } fail(msg) { - this.rec.fail(new Error(msg)); + this.rec.expectationFailed(new Error(msg)); } async immediateAsyncExpectation(fn) { @@ -63,18 +63,18 @@ expectErrorValue(expectedName, ex, niceStack) { if (!(ex instanceof Error)) { - niceStack.message = 'THREW non-error value, of type ' + typeof ex + niceStack.message; - this.rec.fail(niceStack); + niceStack.message = `THREW non-error value, of type ${typeof ex}: ${ex}`; + this.rec.expectationFailed(niceStack); return; } const actualName = ex.name; if (actualName !== expectedName) { - niceStack.message = `THREW ${actualName}, instead of ${expectedName}` + niceStack.message; - this.rec.fail(niceStack); + niceStack.message = `THREW ${actualName}, instead of ${expectedName}: ${ex}`; + this.rec.expectationFailed(niceStack); } else { - niceStack.message = 'OK: threw ' + actualName + niceStack.message; + niceStack.message = `OK: threw ${actualName}${ex.message}`; this.rec.debug(niceStack); } } @@ -85,8 +85,8 @@ try { await p; - niceStack.message = 'DID NOT THROW' + m; - this.rec.fail(niceStack); + niceStack.message = 'DID NOT REJECT' + m; + this.rec.expectationFailed(niceStack); } catch (ex) { niceStack.message = m; this.expectErrorValue(expectedName, ex, niceStack); @@ -99,7 +99,7 @@ try { fn(); - this.rec.fail(new Error('DID NOT THROW' + m)); + this.rec.expectationFailed(new Error('DID NOT THROW' + m)); } catch (ex) { this.expectErrorValue(expectedName, ex, new Error(m)); } @@ -110,7 +110,7 @@ const m = msg ? ': ' + msg : ''; this.rec.debug(new Error('expect OK' + m)); } else { - this.rec.fail(new Error(msg)); + this.rec.expectationFailed(new Error(msg)); } return cond; diff --git a/webgpu/common/framework/gpu/device_pool.js b/webgpu/common/framework/gpu/device_pool.js new file mode 100644 index 0000000..848e580 --- /dev/null +++ b/webgpu/common/framework/gpu/device_pool.js
@@ -0,0 +1,132 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import { assert, raceWithRejectOnTimeout, unreachable, assertReject } from '../util/util.js'; +import { getGPU } from './implementation.js'; + +class TestFailedButDeviceReusable extends Error {} + +export class TestOOMedShouldAttemptGC extends Error {} +const kPopErrorScopeTimeoutMS = 5000; +export class DevicePool { + constructor() { + _defineProperty(this, "failed", false); + + _defineProperty(this, "holder", undefined); + } + + // undefined if "uninitialized" (not yet initialized, or lost) + async acquire() { + assert(!this.failed, 'WebGPU device previously failed to initialize; not retrying'); + + if (this.holder === undefined) { + try { + this.holder = await DevicePool.makeHolder(); + } catch (ex) { + this.failed = true; + throw ex; + } + } + + assert(!this.holder.acquired, 'Device was in use on DevicePool.acquire'); + this.holder.acquired = true; + this.beginErrorScopes(); + return this.holder.device; + } // When a test is done using a device, it's released back into the pool. + // This waits for error scopes, checks their results, and checks for various error conditions. + + + async release(device) { + const holder = this.holder; + assert(holder !== undefined, 'trying to release a device while pool is uninitialized'); + assert(holder.acquired, 'trying to release a device while already released'); + assert(device === holder.device, 'Released device was the wrong device'); + + try { + // Time out if popErrorScope never completes. This could happen due to a browser bug - e.g., + // as of this writing, on Chrome GPU process crash, popErrorScope just hangs. + await raceWithRejectOnTimeout(this.endErrorScopes(), kPopErrorScopeTimeoutMS, 'finalization popErrorScope timed out'); // (Hopefully if the device was lost, it has been reported by the time endErrorScopes() + // has finished (or timed out). If not, it could cause a finite number of extra test + // failures following this one (but should recover eventually).) + + const lostReason = holder.lostReason; + + if (lostReason !== undefined) { + // Fail the current test. + unreachable(`Device was lost: ${lostReason}`); + } + } catch (ex) { + // Any error that isn't explicitly TestFailedButDeviceReusable forces a new device to be + // created for the next test. + if (!(ex instanceof TestFailedButDeviceReusable)) { + this.holder = undefined; + } + + throw ex; + } finally { + // TODO: device.destroy() + // Mark the holder as free. (This only has an effect if the pool still has the holder.) + // This could be done at the top but is done here to guard against async-races during release. + holder.acquired = false; + } + } // Gets a device and creates a DeviceHolder. + // If the device is lost, DeviceHolder.lostReason gets set. + + + static async makeHolder() { + const gpu = getGPU(); + const adapter = await gpu.requestAdapter(); + const holder = { + acquired: false, + device: await adapter.requestDevice(), + lostReason: undefined + }; + holder.device.lost.then(ev => { + holder.lostReason = ev.message; + }); + return holder; + } // Create error scopes that wrap the entire test. + + + beginErrorScopes() { + assert(this.holder !== undefined); + this.holder.device.pushErrorScope('out-of-memory'); + this.holder.device.pushErrorScope('validation'); + } // End the whole-test error scopes. Check that there are no extra error scopes, and that no + // otherwise-uncaptured errors occurred during the test. + + + async endErrorScopes() { + assert(this.holder !== undefined); + let gpuValidationError; + let gpuOutOfMemoryError; + + try { + // May reject if the device was lost. + gpuValidationError = await this.holder.device.popErrorScope(); + gpuOutOfMemoryError = await this.holder.device.popErrorScope(); + } catch (ex) { + assert(this.holder.lostReason !== undefined, "popErrorScope failed, but device.lost hasn't fired (yet)"); + throw ex; + } + + await assertReject(this.holder.device.popErrorScope(), 'There was an extra error scope on the stack after a test'); + + if (gpuValidationError !== null) { + assert(gpuValidationError instanceof GPUValidationError); // Allow the device to be reused. + + throw new TestFailedButDeviceReusable(`Unexpected validation error occurred: ${gpuValidationError.message}`); + } + + if (gpuOutOfMemoryError !== null) { + assert(gpuOutOfMemoryError instanceof GPUOutOfMemoryError); // Don't allow the device to be reused; unexpected OOM could break the device. + + throw new TestOOMedShouldAttemptGC('Unexpected out-of-memory error occurred'); + } + } + +} +//# sourceMappingURL=device_pool.js.map \ No newline at end of file diff --git a/webgpu/common/framework/logging/log_message.js b/webgpu/common/framework/logging/log_message.js new file mode 100644 index 0000000..11f352f --- /dev/null +++ b/webgpu/common/framework/logging/log_message.js
@@ -0,0 +1,50 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import { extractImportantStackTrace } from '../util/stack.js'; +export class LogMessageWithStack extends Error { + constructor(name, ex) { + super(ex.message); + + _defineProperty(this, "stackHidden", false); + + _defineProperty(this, "timesSeen", 1); + + this.name = name; + this.stack = ex.stack; + } + /** Set a flag so the stack is not printed in toJSON(). */ + + + setStackHidden() { + this.stackHidden = true; + } + /** Increment the "seen x times" counter. */ + + + incrementTimesSeen() { + this.timesSeen++; + } + + toJSON() { + let m = this.name + ': '; + + if (!this.stackHidden && this.stack) { + // this.message is already included in this.stack + m += extractImportantStackTrace(this); + } else { + m += this.message; + } + + if (this.timesSeen > 1) { + m += `\n(seen ${this.timesSeen} times with identical stack)`; + } + + return m; + } + +} +//# sourceMappingURL=log_message.js.map \ No newline at end of file diff --git a/webgpu/common/framework/logging/logger.js b/webgpu/common/framework/logging/logger.js new file mode 100644 index 0000000..8d66c55 --- /dev/null +++ b/webgpu/common/framework/logging/logger.js
@@ -0,0 +1,35 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import { version } from '../version.js'; +import { TestCaseRecorder } from './test_case_recorder.js'; +export class Logger { + constructor(debug) { + _defineProperty(this, "debug", void 0); + + _defineProperty(this, "results", new Map()); + + this.debug = debug; + } + + record(name) { + const result = { + status: 'running', + timems: -1 + }; + this.results.set(name, result); + return [new TestCaseRecorder(result, this.debug), result]; + } + + asJSON(space) { + return JSON.stringify({ + version, + results: Array.from(this.results) + }, undefined, space); + } + +} +//# sourceMappingURL=logger.js.map \ No newline at end of file diff --git a/webgpu/common/framework/logging/result.js b/webgpu/common/framework/logging/result.js new file mode 100644 index 0000000..813e781 --- /dev/null +++ b/webgpu/common/framework/logging/result.js
@@ -0,0 +1,4 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ +//# sourceMappingURL=result.js.map \ No newline at end of file
diff --git a/webgpu/common/framework/logging/test_case_recorder.js b/webgpu/common/framework/logging/test_case_recorder.js new file mode 100644 index 0000000..d16af0f --- /dev/null +++ b/webgpu/common/framework/logging/test_case_recorder.js
@@ -0,0 +1,137 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import { SkipTestCase } from '../fixture.js'; +import { now, assert } from '../util/util.js'; +import { LogMessageWithStack } from './log_message.js'; +var LogSeverity; + +(function (LogSeverity) { + LogSeverity[LogSeverity["Pass"] = 0] = "Pass"; + LogSeverity[LogSeverity["Skip"] = 1] = "Skip"; + LogSeverity[LogSeverity["Warn"] = 2] = "Warn"; + LogSeverity[LogSeverity["ExpectFailed"] = 3] = "ExpectFailed"; + LogSeverity[LogSeverity["ValidationFailed"] = 4] = "ValidationFailed"; + LogSeverity[LogSeverity["ThrewException"] = 5] = "ThrewException"; +})(LogSeverity || (LogSeverity = {})); + +const kMaxLogStacks = 2; +/** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */ + +export class TestCaseRecorder { + /** Used to dedup log messages which have identical stacks. */ + constructor(result, debugging) { + _defineProperty(this, "result", void 0); + + _defineProperty(this, "maxLogSeverity", LogSeverity.Pass); + + _defineProperty(this, "startTime", -1); + + _defineProperty(this, "logs", []); + + _defineProperty(this, "logLinesAtCurrentSeverity", 0); + + _defineProperty(this, "debugging", false); + + _defineProperty(this, "messagesForPreviouslySeenStacks", new Map()); + + this.result = result; + this.debugging = debugging; + } + + start() { + assert(this.startTime < 0, 'TestCaseRecorder cannot be reused'); + this.startTime = now(); + } + + finish() { + assert(this.startTime >= 0, 'finish() before start()'); + const timeMilliseconds = now() - this.startTime; // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results. + + this.result.timems = Math.ceil(timeMilliseconds * 1000) / 1000; // Convert numeric enum back to string (but expose 'exception' as 'fail') + + this.result.status = this.maxLogSeverity === LogSeverity.Pass ? 'pass' : this.maxLogSeverity === LogSeverity.Skip ? 'skip' : this.maxLogSeverity === LogSeverity.Warn ? 'warn' : 'fail'; // Everything else is an error + + this.result.logs = this.logs; + } + + injectResult(injectedResult) { + Object.assign(this.result, injectedResult); + } + + debug(ex) { + if (!this.debugging) { + return; + } + + const logMessage = new LogMessageWithStack('DEBUG', ex); + logMessage.setStackHidden(); + this.logImpl(LogSeverity.Pass, logMessage); + } + + skipped(ex) { + this.logImpl(LogSeverity.Skip, new LogMessageWithStack('SKIP', ex)); + } + + warn(ex) { + this.logImpl(LogSeverity.Warn, new LogMessageWithStack('WARN', ex)); + } + + expectationFailed(ex) { + this.logImpl(LogSeverity.ExpectFailed, new LogMessageWithStack('EXPECTATION FAILED', ex)); + } + + validationFailed(ex) { + this.logImpl(LogSeverity.ValidationFailed, new LogMessageWithStack('VALIDATION FAILED', ex)); + } + + threw(ex) { + if (ex instanceof SkipTestCase) { + this.skipped(ex); + return; + } + + this.logImpl(LogSeverity.ThrewException, new LogMessageWithStack('EXCEPTION', ex)); + } + + logImpl(level, logMessage) { + // Deduplicate errors with the exact same stack + if (logMessage.stack) { + const seen = this.messagesForPreviouslySeenStacks.get(logMessage.stack); + + if (seen) { + seen.incrementTimesSeen(); + return; + } + + this.messagesForPreviouslySeenStacks.set(logMessage.stack, logMessage); + } // Mark printStack=false for all logs except 2 at the highest severity + + + if (level > this.maxLogSeverity) { + this.logLinesAtCurrentSeverity = 0; + this.maxLogSeverity = level; + + if (!this.debugging) { + // Go back and turn off printStack for everything of a lower log level + for (const log of this.logs) { + log.setStackHidden(); + } + } + } + + if (level < this.maxLogSeverity || this.logLinesAtCurrentSeverity >= kMaxLogStacks) { + if (!this.debugging) { + logMessage.setStackHidden(); + } + } + + this.logs.push(logMessage); + this.logLinesAtCurrentSeverity++; + } + +} +//# sourceMappingURL=test_case_recorder.js.map \ No newline at end of file diff --git a/webgpu/common/framework/params_builder.js b/webgpu/common/framework/params_builder.js new file mode 100644 index 0000000..10dd239 --- /dev/null +++ b/webgpu/common/framework/params_builder.js
@@ -0,0 +1,111 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +let _Symbol$iterator; + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import { publicParamsEquals } from './params_utils.js'; +import { assert } from './util/util.js'; // https://stackoverflow.com/a/56375136 + +export function poptions(name, values) { + const iter = makeReusableIterable(function* () { + for (const value of values) { + yield { + [name]: value + }; + } + }); + return iter; +} +export function pbool(name) { + return poptions(name, [false, true]); +} +export function params() { + return new ParamsBuilder(); +} +_Symbol$iterator = Symbol.iterator; +export class ParamsBuilder { + constructor() { + _defineProperty(this, "paramSpecs", [{}]); + } + + [_Symbol$iterator]() { + const iter = this.paramSpecs[Symbol.iterator](); + return iter; + } + + combine(newParams) { + const paramSpecs = this.paramSpecs; + this.paramSpecs = makeReusableIterable(function* () { + for (const a of paramSpecs) { + for (const b of newParams) { + yield mergeParams(a, b); + } + } + }); + return this; + } + + expand(expander) { + const paramSpecs = this.paramSpecs; + this.paramSpecs = makeReusableIterable(function* () { + for (const a of paramSpecs) { + for (const b of expander(a)) { + yield mergeParams(a, b); + } + } + }); + return this; + } + + filter(pred) { + const paramSpecs = this.paramSpecs; + this.paramSpecs = makeReusableIterable(function* () { + for (const p of paramSpecs) { + if (pred(p)) { + yield p; + } + } + }); + return this; + } + + unless(pred) { + return this.filter(x => !pred(x)); + } + + exclude(exclude) { + const excludeArray = Array.from(exclude); + const paramSpecs = this.paramSpecs; + this.paramSpecs = makeReusableIterable(function* () { + for (const p of paramSpecs) { + if (excludeArray.every(e => !publicParamsEquals(p, e))) { + yield p; + } + } + }); + return this; + } + +} // If you create an Iterable by calling a generator function (e.g. in IIFE), it is exhausted after +// one use. This just wraps a generator function in an object so it be iterated multiple times. + +function makeReusableIterable(generatorFn) { + return { + [Symbol.iterator]: generatorFn + }; +} + +// (keyof A & keyof B) is not empty, so they overlapped +function mergeParams(a, b) { + for (const key of Object.keys(a)) { + assert(!(key in b), 'Duplicate key: ' + key); + } + + return { ...a, + ...b + }; +} +//# sourceMappingURL=params_builder.js.map \ No newline at end of file diff --git a/webgpu/common/framework/params_utils.js b/webgpu/common/framework/params_utils.js index cde8984..161feb5 100644 --- a/webgpu/common/framework/params_utils.js +++ b/webgpu/common/framework/params_utils.js
@@ -2,71 +2,25 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -import { objectEquals } from './util/util.js'; +import { comparePublicParamsPaths, Ordering } from './query/compare.js'; +import { kWildcard, kParamSeparator, kParamKVSeparator } from './query/separators.js'; // Consider adding more types here if needed + +export function paramKeyIsPublic(key) { + return !key.startsWith('_'); +} export function extractPublicParams(params) { const publicParams = {}; for (const k of Object.keys(params)) { - if (!k.startsWith('_')) { + if (paramKeyIsPublic(k)) { publicParams[k] = params[k]; } } return publicParams; } -export function stringifyPublicParams(p) { - if (p === null || paramsEquals(p, {})) { - return ''; - } - - return JSON.stringify(extractPublicParams(p)); -} -export function paramsEquals(x, y) { - if (x === y) { - return true; - } - - if (x === null) { - x = {}; - } - - if (y === null) { - y = {}; - } - - for (const xk of Object.keys(x)) { - if (x[xk] !== undefined && !y.hasOwnProperty(xk)) { - return false; - } - - if (!objectEquals(x[xk], y[xk])) { - return false; - } - } - - for (const yk of Object.keys(y)) { - if (y[yk] !== undefined && !x.hasOwnProperty(yk)) { - return false; - } - } - - return true; -} -export function paramsSupersets(sup, sub) { - if (sub === null) { - return true; - } - - if (sup === null) { - sup = {}; - } - - for (const k of Object.keys(sub)) { - if (!sup.hasOwnProperty(k) || sup[k] !== sub[k]) { - return false; - } - } - - return true; +export const badParamValueChars = new RegExp('[' + kParamKVSeparator + kParamSeparator + kWildcard + ']'); +export function publicParamsEquals(x, y) { + return comparePublicParamsPaths(x, y) === Ordering.Equal; } //# sourceMappingURL=params_utils.js.map \ No newline at end of file diff --git a/webgpu/common/framework/query/compare.js b/webgpu/common/framework/query/compare.js new file mode 100644 index 0000000..2cbe6cc --- /dev/null +++ b/webgpu/common/framework/query/compare.js
@@ -0,0 +1,96 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { paramKeyIsPublic } from '../params_utils.js'; +import { assert, objectEquals } from '../util/util.js'; +export let Ordering; // Compares two queries for their ordering (which is used to build the tree). +// See src/unittests/query_compare.spec.ts for examples. + +(function (Ordering) { + Ordering[Ordering["Unordered"] = 0] = "Unordered"; + Ordering[Ordering["StrictSuperset"] = 1] = "StrictSuperset"; + Ordering[Ordering["Equal"] = 2] = "Equal"; + Ordering[Ordering["StrictSubset"] = 3] = "StrictSubset"; +})(Ordering || (Ordering = {})); + +export function compareQueries(a, b) { + if (a.suite !== b.suite) { + return Ordering.Unordered; + } + + const filePathOrdering = comparePaths(a.filePathParts, b.filePathParts); + + if (filePathOrdering !== Ordering.Equal || a.isMultiFile || b.isMultiFile) { + return compareOneLevel(filePathOrdering, a.isMultiFile, b.isMultiFile); + } + + assert('testPathParts' in a && 'testPathParts' in b); + const testPathOrdering = comparePaths(a.testPathParts, b.testPathParts); + + if (testPathOrdering !== Ordering.Equal || a.isMultiTest || b.isMultiTest) { + return compareOneLevel(testPathOrdering, a.isMultiTest, b.isMultiTest); + } + + assert('params' in a && 'params' in b); + const paramsPathOrdering = comparePublicParamsPaths(a.params, b.params); + + if (paramsPathOrdering !== Ordering.Equal || a.isMultiCase || b.isMultiCase) { + return compareOneLevel(paramsPathOrdering, a.isMultiCase, b.isMultiCase); + } + + return Ordering.Equal; +} // Compares a single level of a query. +// "IsBig" means the query is big relative to the level, e.g. for test-level: +// anything >= suite:a,* is big +// anything <= suite:a:* is small + +function compareOneLevel(ordering, aIsBig, bIsBig) { + assert(ordering !== Ordering.Equal || aIsBig || bIsBig); + if (ordering === Ordering.Unordered) return Ordering.Unordered; + if (aIsBig && bIsBig) return ordering; + if (!aIsBig && !bIsBig) return Ordering.Unordered; // Equal case is already handled + // Exactly one of (a, b) is big. + + if (aIsBig && ordering !== Ordering.StrictSubset) return Ordering.StrictSuperset; + if (bIsBig && ordering !== Ordering.StrictSuperset) return Ordering.StrictSubset; + return Ordering.Unordered; +} + +function comparePaths(a, b) { + const shorter = Math.min(a.length, b.length); + + for (let i = 0; i < shorter; ++i) { + if (a[i] !== b[i]) { + return Ordering.Unordered; + } + } + + if (a.length === b.length) { + return Ordering.Equal; + } else if (a.length < b.length) { + return Ordering.StrictSuperset; + } else { + return Ordering.StrictSubset; + } +} + +export function comparePublicParamsPaths(a, b) { + const aKeys = Object.keys(a).filter(k => paramKeyIsPublic(k)); + const commonKeys = new Set(aKeys.filter(k => k in b)); + + for (const k of commonKeys) { + if (!objectEquals(a[k], b[k])) { + return Ordering.Unordered; + } + } + + const bKeys = Object.keys(b).filter(k => paramKeyIsPublic(k)); + const aRemainingKeys = aKeys.length - commonKeys.size; + const bRemainingKeys = bKeys.length - commonKeys.size; + if (aRemainingKeys === 0 && bRemainingKeys === 0) return Ordering.Equal; + if (aRemainingKeys === 0) return Ordering.StrictSuperset; + if (bRemainingKeys === 0) return Ordering.StrictSubset; + return Ordering.Unordered; +} +//# sourceMappingURL=compare.js.map \ No newline at end of file diff --git a/webgpu/common/framework/query/encode_selectively.js b/webgpu/common/framework/query/encode_selectively.js new file mode 100644 index 0000000..d67f104 --- /dev/null +++ b/webgpu/common/framework/query/encode_selectively.js
@@ -0,0 +1,28 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +// Encodes a stringified TestQuery so that it can be placed in a `?q=` parameter in a URL. +// encodeURIComponent encodes in accordance with `application/x-www-form-urlencoded`, but URLs don't +// actually have to be as strict as HTML form encoding (we interpret this purely from JavaScript). +// So we encode the component, then selectively convert some %-encoded escape codes back to their +// original form for readability/copyability. +export function encodeURIComponentSelectively(s) { + let ret = encodeURIComponent(s); + ret = ret.replace(/%22/g, '"'); // for JSON strings + + ret = ret.replace(/%2C/g, ','); // for path separator, and JSON arrays + + ret = ret.replace(/%3A/g, ':'); // for big separator + + ret = ret.replace(/%3B/g, ';'); // for param separator + + ret = ret.replace(/%3D/g, '='); // for params (k=v) + + ret = ret.replace(/%5B/g, '['); // for JSON arrays + + ret = ret.replace(/%5D/g, ']'); // for JSON arrays + + return ret; +} +//# sourceMappingURL=encode_selectively.js.map \ No newline at end of file diff --git a/webgpu/common/framework/query/parseQuery.js b/webgpu/common/framework/query/parseQuery.js new file mode 100644 index 0000000..758227c --- /dev/null +++ b/webgpu/common/framework/query/parseQuery.js
@@ -0,0 +1,122 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js'; +import { assert } from '../util/util.js'; +import { TestQueryMultiFile, TestQueryMultiTest, TestQueryMultiCase, TestQuerySingleCase } from './query.js'; +import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js'; +import { validQueryPart } from './validQueryPart.js'; +export function parseQuery(s) { + try { + return parseQueryImpl(s); + } catch (ex) { + ex.message += '\n on: ' + s; + throw ex; + } +} + +function parseQueryImpl(s) { + // Undo encodeURIComponentSelectively + s = decodeURIComponent(s); // bigParts are: suite, group, test, params (note kBigSeparator could appear in params) + + const [suite, fileString, testString, paramsString] = s.split(kBigSeparator, 4); + assert(fileString !== undefined, `filter string must have at least one ${kBigSeparator}`); + const { + parts: file, + wildcard: filePathHasWildcard + } = parseBigPart(fileString, kPathSeparator); + + if (testString === undefined) { + // Query is file-level + assert(filePathHasWildcard, `File-level query without wildcard ${kWildcard}. Did you want a file-level query \ +(append ${kPathSeparator}${kWildcard}) or test-level query (append ${kBigSeparator}${kWildcard})?`); + return new TestQueryMultiFile(suite, file); + } + + assert(!filePathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`); + const { + parts: test, + wildcard: testPathHasWildcard + } = parseBigPart(testString, kPathSeparator); + + if (paramsString === undefined) { + // Query is test-level + assert(testPathHasWildcard, `Test-level query without wildcard ${kWildcard}; did you want a test-level query \ +(append ${kPathSeparator}${kWildcard}) or case-level query (append ${kBigSeparator}${kWildcard})?`); + assert(file.length > 0, 'File part of test-level query was empty (::)'); + return new TestQueryMultiTest(suite, file, test); + } // Query is case-level + + + assert(!testPathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`); + const { + parts: paramsParts, + wildcard: paramsHasWildcard + } = parseBigPart(paramsString, kParamSeparator); + assert(test.length > 0, 'Test part of case-level query was empty (::)'); + const params = {}; + + for (const paramPart of paramsParts) { + const [k, v] = parseSingleParam(paramPart); + assert(validQueryPart.test(k), 'param key names must match ' + validQueryPart); + params[k] = v; + } + + if (paramsHasWildcard) { + return new TestQueryMultiCase(suite, file, test, params); + } else { + return new TestQuerySingleCase(suite, file, test, params); + } +} // webgpu:a,b,* or webgpu:a,b,c:* + + +const kExampleQueries = `\ +webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}${kWildcard} or \ +webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}c${kBigSeparator}${kWildcard}`; + +function parseBigPart(s, separator) { + if (s === '') { + return { + parts: [], + wildcard: false + }; + } + + const parts = s.split(separator); + let endsWithWildcard = false; + + for (const [i, part] of parts.entries()) { + if (i === parts.length - 1) { + endsWithWildcard = part === kWildcard; + } + + assert(part.indexOf(kWildcard) === -1 || endsWithWildcard, `Wildcard ${kWildcard} must be complete last part of a path (e.g. ${kExampleQueries})`); + } + + if (endsWithWildcard) { + // Remove the last element of the array (which is just the wildcard). + parts.length = parts.length - 1; + } + + return { + parts, + wildcard: endsWithWildcard + }; +} + +function parseSingleParam(paramSubstring) { + assert(paramSubstring !== '', 'Param in a query must not be blank (is there a trailing comma?)'); + const i = paramSubstring.indexOf('='); + assert(i !== -1, 'Param in a query must be of form key=value'); + const k = paramSubstring.substring(0, i); + assert(paramKeyIsPublic(k), 'Param in a query must not be private (start with _)'); + const v = paramSubstring.substring(i + 1); + return [k, parseSingleParamValue(v)]; +} + +function parseSingleParamValue(s) { + assert(!badParamValueChars.test(s), `param value must not match ${badParamValueChars} - was ${s}`); + return s === 'undefined' ? undefined : JSON.parse(s); +} +//# sourceMappingURL=parseQuery.js.map \ No newline at end of file diff --git a/webgpu/common/framework/query/query.js b/webgpu/common/framework/query/query.js new file mode 100644 index 0000000..b80b690 --- /dev/null +++ b/webgpu/common/framework/query/query.js
@@ -0,0 +1,89 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import { assert } from '../util/util.js'; +import { encodeURIComponentSelectively } from './encode_selectively.js'; +import { kBigSeparator, kPathSeparator, kWildcard, kParamSeparator } from './separators.js'; +import { stringifyPublicParams } from './stringify_params.js'; +export class TestQueryMultiFile { + constructor(suite, file) { + _defineProperty(this, "isMultiFile", true); + + _defineProperty(this, "suite", void 0); + + _defineProperty(this, "filePathParts", void 0); + + this.suite = suite; + this.filePathParts = [...file]; + } + + toString() { + return encodeURIComponentSelectively(this.toStringHelper().join(kBigSeparator)); + } + + toHTML() { + return this.toStringHelper().join(kBigSeparator + '<wbr>'); + } + + toStringHelper() { + return [this.suite, [...this.filePathParts, kWildcard].join(kPathSeparator)]; + } + +} +export class TestQueryMultiTest extends TestQueryMultiFile { + constructor(suite, file, test) { + super(suite, file); + + _defineProperty(this, "isMultiFile", false); + + _defineProperty(this, "isMultiTest", true); + + _defineProperty(this, "testPathParts", void 0); + + assert(file.length > 0, 'multi-test (or finer) query must have file-path'); + this.testPathParts = [...test]; + } + + toStringHelper() { + return [this.suite, this.filePathParts.join(kPathSeparator), [...this.testPathParts, kWildcard].join(kPathSeparator)]; + } + +} +export class TestQueryMultiCase extends TestQueryMultiTest { + constructor(suite, file, test, params) { + super(suite, file, test); + + _defineProperty(this, "isMultiTest", false); + + _defineProperty(this, "isMultiCase", true); + + _defineProperty(this, "params", void 0); + + assert(test.length > 0, 'multi-case (or finer) query must have test-path'); + this.params = { ...params + }; + } + + toStringHelper() { + const paramsParts = stringifyPublicParams(this.params); + return [this.suite, this.filePathParts.join(kPathSeparator), this.testPathParts.join(kPathSeparator), [...paramsParts, kWildcard].join(kParamSeparator)]; + } + +} +export class TestQuerySingleCase extends TestQueryMultiCase { + constructor(...args) { + super(...args); + + _defineProperty(this, "isMultiCase", false); + } + + toStringHelper() { + const paramsParts = stringifyPublicParams(this.params); + return [this.suite, this.filePathParts.join(kPathSeparator), this.testPathParts.join(kPathSeparator), paramsParts.join(kParamSeparator)]; + } + +} +//# sourceMappingURL=query.js.map \ No newline at end of file diff --git a/webgpu/common/framework/query/separators.js b/webgpu/common/framework/query/separators.js new file mode 100644 index 0000000..4782fe4 --- /dev/null +++ b/webgpu/common/framework/query/separators.js
@@ -0,0 +1,14 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +export const kBigSeparator = ':'; // Separator between big parts: suite:file:test:case + +export const kPathSeparator = ','; // Separator between path,to,file or path,to,test + +export const kParamSeparator = ';'; // Separator between k=v;k=v + +export const kParamKVSeparator = '='; // Separator between key and value in k=v + +export const kWildcard = '*'; // Final wildcard, if query is not single-case +//# sourceMappingURL=separators.js.map \ No newline at end of file
diff --git a/webgpu/common/framework/query/stringify_params.js b/webgpu/common/framework/query/stringify_params.js new file mode 100644 index 0000000..b5503ff --- /dev/null +++ b/webgpu/common/framework/query/stringify_params.js
@@ -0,0 +1,20 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js'; +import { assert } from '../util/util.js'; +import { kParamKVSeparator } from './separators.js'; +export function stringifyPublicParams(p) { + return Object.keys(p).filter(k => paramKeyIsPublic(k)).map(k => stringifySingleParam(k, p[k])); +} +export function stringifySingleParam(k, v) { + return `${k}${kParamKVSeparator}${stringifySingleParamValue(v)}`; +} + +function stringifySingleParamValue(v) { + const s = v === undefined ? 'undefined' : JSON.stringify(v); + assert(!badParamValueChars.test(s), `JSON.stringified param value must not match ${badParamValueChars} - was ${s}`); + return s; +} +//# sourceMappingURL=stringify_params.js.map \ No newline at end of file diff --git a/webgpu/common/framework/query/validQueryPart.js b/webgpu/common/framework/query/validQueryPart.js new file mode 100644 index 0000000..f51ebad --- /dev/null +++ b/webgpu/common/framework/query/validQueryPart.js
@@ -0,0 +1,7 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +// Applies to group parts, test parts, params keys. +export const validQueryPart = /^[a-zA-Z0-9_]+$/; +//# sourceMappingURL=validQueryPart.js.map \ No newline at end of file
diff --git a/webgpu/common/framework/test_group.js b/webgpu/common/framework/test_group.js index 8837b1e..ef34b5f 100644 --- a/webgpu/common/framework/test_group.js +++ b/webgpu/common/framework/test_group.js
@@ -4,12 +4,20 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -import { allowedTestNameCharacters } from './allowed_characters.js'; -import { extractPublicParams, paramsEquals } from './params_utils.js'; -import { checkPublicParamType } from './url_query.js'; +import { extractPublicParams, publicParamsEquals } from './params_utils.js'; +import { kPathSeparator } from './query/separators.js'; +import { stringifyPublicParams } from './query/stringify_params.js'; +import { validQueryPart } from './query/validQueryPart.js'; import { assert } from './util/util.js'; -const validNames = new RegExp('^[' + allowedTestNameCharacters + ']+$'); -export class TestGroup { +export function makeTestGroup(fixture) { + return new TestGroup(fixture); +} // Interface for running tests + +export function makeTestGroupForUnitTesting(fixture) { + return new TestGroup(fixture); +} + +class TestGroup { constructor(fixture) { _defineProperty(this, "fixture", void 0); @@ -20,14 +28,13 @@ this.fixture = fixture; } - *iterate(log) { + *iterate() { for (const test of this.tests) { - yield* test.iterate(log); + yield* test.iterate(); } } checkName(name) { - assert(validNames.test(name), `Invalid test name ${name}; must match [${validNames}]+`); assert( // Shouldn't happen due to the rule above. Just makes sure that treated // unencoded strings as encoded strings is OK. name === decodeURIComponent(name), `Not decodeURIComponent-idempotent: ${name} !== ${decodeURIComponent(name)}`); @@ -36,86 +43,98 @@ } // TODO: This could take a fixture, too, to override the one for the group. - test(name, fn) { - // Replace spaces with underscores for readability. - assert(name.indexOf('_') === -1, 'Invalid test name ${name}: contains underscore (use space)'); - name = name.replace(/ /g, '_'); + test(name) { this.checkName(name); - const test = new Test(name, this.fixture, fn); + const parts = name.split(kPathSeparator); + + for (const p of parts) { + assert(validQueryPart.test(p), `Invalid test name part ${p}; must match ${validQueryPart}`); + } + + const test = new TestBuilder(parts, this.fixture); this.tests.push(test); return test; } -} // This test is created when it's inserted, but may be parameterized afterward (.params()). + checkCaseNamesAndDuplicates() { + for (const test of this.tests) { + test.checkCaseNamesAndDuplicates(); + } + } -class Test { - constructor(name, fixture, fn) { - _defineProperty(this, "name", void 0); +} + +class TestBuilder { + constructor(testPath, fixture) { + _defineProperty(this, "testPath", void 0); _defineProperty(this, "fixture", void 0); - _defineProperty(this, "fn", void 0); + _defineProperty(this, "testFn", void 0); - _defineProperty(this, "cases", null); + _defineProperty(this, "cases", undefined); - this.name = name; + this.testPath = testPath; this.fixture = fixture; - this.fn = fn; } - params(specs) { - assert(this.cases === null, 'test case is already parameterized'); - const cases = Array.from(specs); - const seen = []; // This is n^2. + fn(fn) { + this.testFn = fn; + } - for (const spec of cases) { - const publicParams = extractPublicParams(spec); // Check type of public params: can only be (currently): - // number, string, boolean, undefined, number[] + checkCaseNamesAndDuplicates() { + if (this.cases === undefined) { + return; + } // This is n^2. - for (const v of Object.values(publicParams)) { - checkPublicParamType(v); - } - assert(!seen.some(x => paramsEquals(x, publicParams)), 'Duplicate test case params'); - seen.push(publicParams); + const seen = []; + + for (const testcase of this.cases) { + // stringifyPublicParams also checks for invalid params values + const testcaseString = stringifyPublicParams(testcase); + assert(!seen.some(x => publicParamsEquals(x, testcase)), `Duplicate public test case params: ${testcaseString}`); + seen.push(testcase); } - - this.cases = cases; } - *iterate(rec) { - for (const params of this.cases || [null]) { - yield new RunCaseSpecific(rec, this.name, params, this.fixture, this.fn); + params(casesIterable) { + assert(this.cases === undefined, 'test case is already parameterized'); + this.cases = Array.from(casesIterable); + return this; + } + + *iterate() { + assert(this.testFn !== undefined, 'internal error'); + + for (const params of this.cases || [{}]) { + yield new RunCaseSpecific(this.testPath, params, this.fixture, this.testFn); } } } class RunCaseSpecific { - constructor(recorder, test, params, fixture, fn) { + constructor(testPath, params, fixture, fn) { _defineProperty(this, "id", void 0); _defineProperty(this, "params", void 0); - _defineProperty(this, "recorder", void 0); - _defineProperty(this, "fixture", void 0); _defineProperty(this, "fn", void 0); this.id = { - test, - params: params ? extractPublicParams(params) : null + test: testPath, + params: extractPublicParams(params) }; this.params = params; - this.recorder = recorder; this.fixture = fixture; this.fn = fn; } - async run(debug) { - const [rec, res] = this.recorder.record(this.id.test, this.id.params); - rec.start(debug); + async run(rec) { + rec.start(); try { const inst = new this.fixture(rec, this.params || {}); @@ -130,17 +149,12 @@ } catch (ex) { // There was an exception from constructor, init, test, or finalize. // An error from init or test may have been a SkipTestCase. - // An error from finalize may have been an eventualAsyncExpectation failure. + // An error from finalize may have been an eventualAsyncExpectation failure + // or unexpected validation/OOM error from the GPUDevice. rec.threw(ex); } rec.finish(); - return res; - } - - injectResult(result) { - const [, res] = this.recorder.record(this.id.test, this.id.params); - Object.assign(res, result); } } diff --git a/webgpu/common/framework/test_suite_listing.js b/webgpu/common/framework/test_suite_listing.js new file mode 100644 index 0000000..99c9002 --- /dev/null +++ b/webgpu/common/framework/test_suite_listing.js
@@ -0,0 +1,4 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ +//# sourceMappingURL=test_suite_listing.js.map \ No newline at end of file
diff --git a/webgpu/common/framework/tree.js b/webgpu/common/framework/tree.js index 8f96c98..2f4329c 100644 --- a/webgpu/common/framework/tree.js +++ b/webgpu/common/framework/tree.js
@@ -2,91 +2,301 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -import { stringifyPublicParams } from './params_utils.js'; +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -// e.g. iteratePath('a/b/c/d', ':') yields ['a/', 'a/b/', 'a/b/c/', 'a/b/c/d:'] -function* iteratePath(path, terminator) { - const parts = path.split('/'); +import { compareQueries, Ordering } from './query/compare.js'; +import { TestQueryMultiCase, TestQuerySingleCase, TestQueryMultiFile, TestQueryMultiTest } from './query/query.js'; +import { stringifySingleParam } from './query/stringify_params.js'; +import { assert } from './util/util.js'; // `loadTreeForQuery()` loads a TestTree for a given queryToLoad. +// The resulting tree is a linked-list all the way from `suite:*` to queryToLoad, +// and under queryToLoad is a tree containing every case matched by queryToLoad. +// +// `subqueriesToExpand` influences the `collapsible` flag on nodes in the resulting tree. +// A node is considered "collapsible" if none of the subqueriesToExpand is a StrictSubset +// of that node. +// +// In WebKit/Blink-style web_tests, an expectation file marks individual cts.html "variants" as +// "Failure", "Crash", etc. +// By passing in the list of expectations as the subqueriesToExpand, we can programmatically +// subdivide the cts.html "variants" list to be able to implement arbitrarily-fine suppressions +// (instead of having to suppress entire test files, which would lose a lot of coverage). +// +// `iterateCollapsedQueries()` produces the list of queries for the variants list. +// +// Though somewhat complicated, this system has important benefits: +// - Avoids having to suppress entire test files, which would cause large test coverage loss. +// - Minimizes the number of page loads needed for fine-grained suppressions. +// (In the naive case, we could do one page load per test case - but the test suite would +// take impossibly long to run.) +// - Enables developers to put any number of tests in one file as appropriate, without worrying +// about expectation granularity. - if (parts.length > 1) { - let partial = parts[0] + '/'; - yield partial; +export class TestTree { + constructor(root) { + _defineProperty(this, "root", void 0); - for (let i = 1; i < parts.length - 1; ++i) { - partial += parts[i] + '/'; - yield partial; - } // Path ends in '/' (so is a README). + this.root = root; + } + iterateCollapsedQueries() { + return TestTree.iterateSubtreeCollapsedQueries(this.root); + } - if (parts[parts.length - 1] === '') { - return; + iterateLeaves() { + return TestTree.iterateSubtreeLeaves(this.root); + } + + toString() { + return TestTree.subtreeToString('(root)', this.root, ''); + } + + static *iterateSubtreeCollapsedQueries(subtree) { + for (const [, child] of subtree.children) { + if ('children' in child && !child.collapsible) { + yield* TestTree.iterateSubtreeCollapsedQueries(child); + } else { + yield child.query; + } } } - yield path + terminator; -} - -export function treeFromFilterResults(log, listing) { - function getOrInsert(n, k) { - const children = n.children; - - if (children.has(k)) { - return children.get(k); + static *iterateSubtreeLeaves(subtree) { + for (const [, child] of subtree.children) { + if ('children' in child) { + yield* TestTree.iterateSubtreeLeaves(child); + } else { + yield child; + } } - - const v = { - children: new Map() - }; - children.set(k, v); - return v; } - const tree = { - children: new Map() - }; + static subtreeToString(name, tree, indent) { + const collapsible = 'run' in tree ? '>' : tree.collapsible ? '+' : '-'; + let s = indent + `${collapsible} ${JSON.stringify(name)} => ` + `${tree.query} ${JSON.stringify(tree.query)}`; - for (const f of listing) { - const files = getOrInsert(tree, f.id.suite + ':'); - - if (f.id.path === '') { - // This is a suite README. - files.description = f.spec.description.trim(); - continue; - } - - let tests = files; - - for (const path of iteratePath(f.id.path, ':')) { - tests = getOrInsert(tests, f.id.suite + ':' + path); - } - - if (f.spec.description) { - // This is a directory README or spec file. - tests.description = f.spec.description.trim(); - } - - if (!('g' in f.spec)) { - // This is a directory README. - continue; - } - - const [tRec] = log.record(f.id); - const fId = f.id.suite + ':' + f.id.path; - - for (const t of f.spec.g.iterate(tRec)) { - let cases = tests; - - for (const path of iteratePath(t.id.test, '~')) { - cases = getOrInsert(cases, fId + ':' + path); + if ('children' in tree) { + if (tree.description !== undefined) { + s += indent + `\n | ${JSON.stringify(tree.description)}`; } - const p = stringifyPublicParams(t.id.params); - cases.children.set(fId + ':' + t.id.test + '=' + p, { - runCase: t - }); + for (const [name, child] of tree.children) { + s += '\n' + TestTree.subtreeToString(name, child, indent + ' '); + } } + + return s; + } + +} // TODO: Consider having subqueriesToExpand actually impact the depth-order of params in the tree. + +export async function loadTreeForQuery(loader, queryToLoad, subqueriesToExpand) { + const suite = queryToLoad.suite; + const specs = await loader.listing(suite); + const subqueriesToExpandEntries = Array.from(subqueriesToExpand.entries()); + const seenSubqueriesToExpand = new Array(subqueriesToExpand.length); + seenSubqueriesToExpand.fill(false); + + const isCollapsible = subquery => subqueriesToExpandEntries.every(([i, toExpand]) => { + const ordering = compareQueries(toExpand, subquery); // If toExpand == subquery, no expansion is needed (but it's still "seen"). + + if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true; + return ordering !== Ordering.StrictSubset; + }); // L0 = suite-level, e.g. suite:* + // L1 = file-level, e.g. suite:a,b:* + // L2 = test-level, e.g. suite:a,b:c,d:* + // L3 = case-level, e.g. suite:a,b:c,d: + + + let foundCase = false; // L0 is suite:* + + const subtreeL0 = makeTreeForSuite(suite); + isCollapsible(subtreeL0.query); // mark seenSubqueriesToExpand + + for (const entry of specs) { + if (entry.file.length === 0 && 'readme' in entry) { + // Suite-level readme. + assert(subtreeL0.description === undefined); + subtreeL0.description = entry.readme.trim(); + continue; + } + + { + const queryL1 = new TestQueryMultiFile(suite, entry.file); + const orderingL1 = compareQueries(queryL1, queryToLoad); + + if (orderingL1 === Ordering.Unordered) { + // File path is not matched by this query. + continue; + } + } + + if ('readme' in entry) { + // Entry is a README that is an ancestor or descendant of the query. + // (It's included for display in the standalone runner.) + // readmeSubtree is suite:a,b,* + // (This is always going to dedup with a file path, if there are any test spec files under + // the directory that has the README). + const readmeSubtree = addSubtreeForDirPath(subtreeL0, entry.file); + assert(readmeSubtree.description === undefined); + readmeSubtree.description = entry.readme.trim(); + continue; + } // Entry is a spec file. + + + const spec = await loader.importSpecFile(queryToLoad.suite, entry.file); + const description = spec.description.trim(); // subtreeL1 is suite:a,b:* + + const subtreeL1 = addSubtreeForFilePath(subtreeL0, entry.file, description, isCollapsible); // TODO: If tree generation gets too slow, avoid actually iterating the cases in a file + // if there's no need to (based on the subqueriesToExpand). + + for (const t of spec.g.iterate()) { + { + const queryL3 = new TestQuerySingleCase(suite, entry.file, t.id.test, t.id.params); + const orderingL3 = compareQueries(queryL3, queryToLoad); + + if (orderingL3 === Ordering.Unordered || orderingL3 === Ordering.StrictSuperset) { + // Case is not matched by this query. + continue; + } + } // subtreeL2 is suite:a,b:c,d:* + + const subtreeL2 = addSubtreeForTestPath(subtreeL1, t.id.test, isCollapsible); // Leaf for case is suite:a,b:c,d:x=1;y=2 + + addLeafForCase(subtreeL2, t, isCollapsible); + foundCase = true; + } + } + + const tree = new TestTree(subtreeL0); + + for (const [i, sq] of subqueriesToExpandEntries) { + const seen = seenSubqueriesToExpand[i]; + assert(seen, `subqueriesToExpand entry did not match anything \ +(can happen due to overlap with another subquery): ${sq.toString()}`); + } + + assert(foundCase, 'Query does not match any cases'); // TODO: Contains lots of single-child subtrees. Consider cleaning those up (as postprocess?). + + return tree; +} + +function makeTreeForSuite(suite) { + return { + query: new TestQueryMultiFile(suite, []), + children: new Map(), + collapsible: false + }; +} + +function addSubtreeForDirPath(tree, file) { + const subqueryFile = []; // To start, tree is suite:* + // This loop goes from that -> suite:a,* -> suite:a,b,* + + for (const part of file) { + subqueryFile.push(part); + tree = getOrInsertSubtree(part, tree, () => { + const query = new TestQueryMultiFile(tree.query.suite, subqueryFile); + return { + query, + collapsible: false + }; + }); } return tree; } + +function addSubtreeForFilePath(tree, file, description, checkCollapsible) { + // To start, tree is suite:* + // This goes from that -> suite:a,* -> suite:a,b,* + tree = addSubtreeForDirPath(tree, file); // This goes from that -> suite:a,b:* + + const subtree = getOrInsertSubtree('', tree, () => { + const query = new TestQueryMultiTest(tree.query.suite, tree.query.filePathParts, []); + return { + query, + description, + collapsible: checkCollapsible(query) + }; + }); + return subtree; +} + +function addSubtreeForTestPath(tree, test, isCollapsible) { + const subqueryTest = []; // To start, tree is suite:a,b:* + // This loop goes from that -> suite:a,b:c,* -> suite:a,b:c,d,* + + for (const part of test) { + subqueryTest.push(part); + tree = getOrInsertSubtree(part, tree, () => { + const query = new TestQueryMultiTest(tree.query.suite, tree.query.filePathParts, subqueryTest); + return { + query, + collapsible: isCollapsible(query) + }; + }); + } // This goes from that -> suite:a,b:c,d:* + + + return getOrInsertSubtree('', tree, () => { + const query = new TestQueryMultiCase(tree.query.suite, tree.query.filePathParts, subqueryTest, {}); + return { + query, + collapsible: isCollapsible(query) + }; + }); +} + +function addLeafForCase(tree, t, checkCollapsible) { + const query = tree.query; + let name = ''; + const subqueryParams = {}; // To start, tree is suite:a,b:c,d:* + // This loop goes from that -> suite:a,b:c,d:x=1;* -> suite:a,b:c,d:x=1;y=2;* + + for (const [k, v] of Object.entries(t.id.params)) { + name = stringifySingleParam(k, v); + subqueryParams[k] = v; + tree = getOrInsertSubtree(name, tree, () => { + const subquery = new TestQueryMultiCase(query.suite, query.filePathParts, query.testPathParts, subqueryParams); + return { + query: subquery, + collapsible: checkCollapsible(subquery) + }; + }); + } // This goes from that -> suite:a,b:c,d:x=1;y=2 + + + const subquery = new TestQuerySingleCase(query.suite, query.filePathParts, query.testPathParts, subqueryParams); + checkCollapsible(subquery); // mark seenSubqueriesToExpand + + insertLeaf(tree, subquery, t); +} + +function getOrInsertSubtree(key, parent, createSubtree) { + let v; + const child = parent.children.get(key); + + if (child !== undefined) { + assert('children' in child); // Make sure cached subtree is not actually a leaf + + v = child; + } else { + v = { ...createSubtree(), + children: new Map() + }; + parent.children.set(key, v); + } + + return v; +} + +function insertLeaf(parent, query, t) { + const key = ''; + const leaf = { + query, + run: rec => t.run(rec) + }; + assert(!parent.children.has(key)); + parent.children.set(key, leaf); +} //# sourceMappingURL=tree.js.map \ No newline at end of file diff --git a/webgpu/common/framework/util/collect_garbage.js b/webgpu/common/framework/util/collect_garbage.js new file mode 100644 index 0000000..83b797a --- /dev/null +++ b/webgpu/common/framework/util/collect_garbage.js
@@ -0,0 +1,51 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { resolveOnTimeout } from './util.js'; +export async function attemptGarbageCollection() { + const w = self; + + if (w.GCController) { + w.GCController.collect(); + return; + } + + if (w.opera && w.opera.collect) { + w.opera.collect(); + return; + } + + try { + w.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).garbageCollect(); + return; + } catch (e) {} + + if (w.gc) { + w.gc(); + return; + } + + if (w.CollectGarbage) { + w.CollectGarbage(); + return; + } + + let i; + + function gcRec(n) { + if (n < 1) return; + let temp = { + i: 'ab' + i + i / 100000 + }; + temp = temp + 'foo'; + gcRec(n - 1); + } + + for (i = 0; i < 1000; i++) { + gcRec(10); + } + + return resolveOnTimeout(35); // Let the event loop run a few frames in case it helps. +} +//# sourceMappingURL=collect_garbage.js.map \ No newline at end of file diff --git a/webgpu/common/framework/util/stack.js b/webgpu/common/framework/util/stack.js index 3d0ec63..99653f8 100644 --- a/webgpu/common/framework/util/stack.js +++ b/webgpu/common/framework/util/stack.js
@@ -2,40 +2,24 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -// Takes a stack trace, and extracts only the first continuous range of lines -// containing '/(webgpu|unittests)/', which should provide only the useful part -// of the stack to the caller (for logging). -export function getStackTrace(e) { +// Returns the stack trace of an Error, but without the extra boilerplate at the bottom +// (e.g. RunCaseSpecific, processTicksAndRejections, etc.), for logging. +export function extractImportantStackTrace(e) { if (!e.stack) { return ''; } - const parts = e.stack.split('\n'); - const stack = []; - const moreStack = []; - let found = false; - const commonRegex = /[\/\\](webgpu|unittests)[\/\\]/; + const lines = e.stack.split('\n'); - for (let i = 0; i < parts.length; ++i) { - const part = parts[i].trim(); - const isSuites = commonRegex.test(part); // approximate + for (let i = lines.length - 1; i >= 0; --i) { + const line = lines[i]; - if (found && !isSuites) { - moreStack.push(part); - } - - if (isSuites) { - if (moreStack.length) { - stack.push(...moreStack); - moreStack.length = 0; - } - - stack.push(part); - found = true; + if (line.indexOf('.spec.') !== -1) { + return lines.slice(0, i + 1).join('\n'); } } - return stack.join('\n'); + return e.stack; } // *** Examples *** // // Node fail() diff --git a/webgpu/common/framework/util/util.js b/webgpu/common/framework/util/util.js index ca64026..b643799 100644 --- a/webgpu/common/framework/util/util.js +++ b/webgpu/common/framework/util/util.js
@@ -5,7 +5,14 @@ import { timeout } from './timeout.js'; export function assert(condition, msg) { if (!condition) { - throw new Error(msg); + throw new Error(msg && (typeof msg === 'string' ? msg : msg())); + } +} +export async function assertReject(p, msg) { + try { + await p; + unreachable(msg); + } catch (ex) {// Assertion OK } } export function unreachable(msg) { @@ -16,10 +23,18 @@ export function now() { return perf.now(); } -export function rejectOnTimeout(ms, msg) { - return new Promise((resolve, reject) => { +export function resolveOnTimeout(ms) { + return new Promise(resolve => { timeout(() => { - reject(new Error(msg)); + resolve(); + }, ms); + }); +} +export class PromiseTimeoutError extends Error {} +export function rejectOnTimeout(ms, msg) { + return new Promise((_resolve, reject) => { + timeout(() => { + reject(new PromiseTimeoutError(msg)); }, ms); }); } diff --git a/webgpu/common/framework/version.js b/webgpu/common/framework/version.js index 159b3cf..f1fe61ed 100644 --- a/webgpu/common/framework/version.js +++ b/webgpu/common/framework/version.js
@@ -1,3 +1,3 @@ // AUTO-GENERATED - DO NOT EDIT. See tools/gen_version. -export const version = '8b01e69c5c7447d2560323f8d2da319db580e733'; +export const version = 'd8d0ca524c80a0086ab70e155b80589a6be2b6f6';
diff --git a/webgpu/common/runtime/helper/test_worker-worker.js b/webgpu/common/runtime/helper/test_worker-worker.js index dd078c3..be3f495 100644 --- a/webgpu/common/runtime/helper/test_worker-worker.js +++ b/webgpu/common/runtime/helper/test_worker-worker.js
@@ -2,27 +2,21 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -import { TestLoader } from '../../framework/loader.js'; -import { Logger } from '../../framework/logger.js'; +import { DefaultTestFileLoader } from '../../framework/file_loader.js'; +import { Logger } from '../../framework/logging/logger.js'; import { assert } from '../../framework/util/util.js'; // should be DedicatedWorkerGlobalScope -const log = new Logger(); -const loader = new TestLoader(); +const loader = new DefaultTestFileLoader(); self.onmessage = async ev => { - const { - query, - debug - } = ev.data; - const files = Array.from((await loader.loadTests([query]))); - assert(files.length === 1, 'worker query resulted in != 1 files'); - const f = files[0]; - const [rec] = log.record(f.id); - assert('g' in f.spec, 'worker query resulted in README'); - const cases = Array.from(f.spec.g.iterate(rec)); - assert(cases.length === 1, 'worker query resulted in != 1 cases'); - const c = cases[0]; - const result = await c.run(debug); + const query = ev.data.query; + const debug = ev.data.debug; + const log = new Logger(debug); + const testcases = Array.from((await loader.loadTests(query))); + assert(testcases.length === 1, 'worker query resulted in != 1 cases'); + const testcase = testcases[0]; + const [rec, result] = log.record(testcase.query.toString()); + await testcase.run(rec); self.postMessage({ query, result diff --git a/webgpu/common/runtime/helper/test_worker.js b/webgpu/common/runtime/helper/test_worker.js index a670300..7e8b9a4 100644 --- a/webgpu/common/runtime/helper/test_worker.js +++ b/webgpu/common/runtime/helper/test_worker.js
@@ -4,13 +4,16 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -import { LogMessageWithStack } from '../../framework/logger.js'; +import { LogMessageWithStack } from '../../framework/logging/log_message.js'; export class TestWorker { - constructor() { + constructor(debug) { + _defineProperty(this, "debug", void 0); + _defineProperty(this, "worker", void 0); _defineProperty(this, "resolvers", new Map()); + this.debug = debug; const selfPath = import.meta.url; const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); const workerPath = selfPathDir + '/test_worker-worker.js'; @@ -33,14 +36,15 @@ }; } - run(query, debug = false) { + async run(rec, query) { this.worker.postMessage({ query, - debug + debug: this.debug }); - return new Promise(resolve => { + const workerResult = await new Promise(resolve => { this.resolvers.set(query, resolve); }); + rec.injectResult(workerResult); } } diff --git a/webgpu/common/runtime/wpt.js b/webgpu/common/runtime/wpt.js index fd02f09..0648a0d 100644 --- a/webgpu/common/runtime/wpt.js +++ b/webgpu/common/runtime/wpt.js
@@ -2,58 +2,56 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -import { TestLoader } from '../framework/loader.js'; -import { Logger } from '../framework/logger.js'; -import { makeQueryString } from '../framework/url_query.js'; +import { DefaultTestFileLoader } from '../framework/file_loader.js'; +import { Logger } from '../framework/logging/logger.js'; import { AsyncMutex } from '../framework/util/async_mutex.js'; +import { assert } from '../framework/util/util.js'; import { optionEnabled } from './helper/options.js'; import { TestWorker } from './helper/test_worker.js'; (async () => { - const loader = new TestLoader(); - const files = await loader.loadTestsFromQuery(window.location.search); - const worker = optionEnabled('worker') ? new TestWorker() : undefined; - const log = new Logger(); + const loader = new DefaultTestFileLoader(); + const qs = new URLSearchParams(window.location.search).getAll('q'); + assert(qs.length === 1, 'currently, there must be exactly one ?q='); + const testcases = await loader.loadTests(qs[0]); + await addWPTTests(testcases); +})(); // Note: async_tests must ALL be added within the same task. This function *must not* be async. + + +function addWPTTests(testcases) { + const worker = optionEnabled('worker') ? new TestWorker(false) : undefined; + const log = new Logger(false); const mutex = new AsyncMutex(); const running = []; - for (const f of files) { - if (!('g' in f.spec)) { - continue; - } + for (const testcase of testcases) { + const name = testcase.query.toString(); - const [rec] = log.record(f.id); + const wpt_fn = function () { + const p = mutex.with(async () => { + const [rec, res] = log.record(name); - for (const t of f.spec.g.iterate(rec)) { - const name = makeQueryString(f.id, t.id); // Note: apparently, async_tests must ALL be added within the same task. + if (worker) { + await worker.run(rec, name); + } else { + await testcase.run(rec); + } - async_test(function () { - const p = mutex.with(async () => { - let r; - - if (worker) { - r = await worker.run(name); - t.injectResult(r); - } else { - r = await t.run(); + this.step(() => { + // Unfortunately, it seems not possible to surface any logs for warn/skip. + if (res.status === 'fail') { + throw (res.logs || []).map(s => s.toJSON()).join('\n\n'); } - - this.step(() => { - // Unfortunately, it seems not possible to surface any logs for warn/skip. - if (r.status === 'fail') { - throw (r.logs || []).map(s => s.toJSON()).join('\n\n'); - } - }); - this.done(); }); - running.push(p); - return p; - }, name); - } + this.done(); + }); + running.push(p); + return p; + }; + + async_test(wpt_fn, name); } - await Promise.all(running); - const resultsElem = document.getElementById('results'); - resultsElem.textContent = log.asJSON(2); -})(); + return Promise.all(running).then(() => log); +} //# sourceMappingURL=wpt.js.map \ No newline at end of file diff --git a/webgpu/cts.html b/webgpu/cts.html index 62340be..a06ad79 100644 --- a/webgpu/cts.html +++ b/webgpu/cts.html
@@ -26,49 +26,30 @@ <script src=/resources/testharness.js></script> <script src=/resources/testharnessreport.js></script> -<style> -#results { - font-family: monospace; - width: 100%; - height: 15em; -} - -/* Test Name column */ -#results > tbody > tr > td:nth-child(2) { - word-break: break-word; -} - -/* Message column */ -#results > tbody > tr > td:nth-child(3) { - white-space: pre-wrap; - word-break: break-word; -} -</style> - -<textarea id=results></textarea> <script type=module src=/webgpu/common/runtime/wpt.js></script> -<meta name=variant content='?q=webgpu:api/operation/buffers/create_mapped'> -<meta name=variant content='?q=webgpu:api/operation/buffers/map'> -<meta name=variant content='?q=webgpu:api/operation/buffers/map_detach'> -<meta name=variant content='?q=webgpu:api/operation/buffers/map_oom'> -<meta name=variant content='?q=webgpu:api/operation/command_buffer/basic'> -<meta name=variant content='?q=webgpu:api/operation/command_buffer/copies'> -<meta name=variant content='?q=webgpu:api/operation/command_buffer/render/basic'> -<meta name=variant content='?q=webgpu:api/operation/fences'> -<meta name=variant content='?q=webgpu:api/validation/createBindGroup'> -<meta name=variant content='?q=webgpu:api/validation/createBindGroupLayout'> -<meta name=variant content='?q=webgpu:api/validation/createPipelineLayout'> -<meta name=variant content='?q=webgpu:api/validation/createTexture'> -<meta name=variant content='?q=webgpu:api/validation/createView'> -<meta name=variant content='?q=webgpu:api/validation/error_scope'> -<meta name=variant content='?q=webgpu:api/validation/fences'> -<meta name=variant content='?q=webgpu:api/validation/queue_submit'> -<meta name=variant content='?q=webgpu:api/validation/render_pass_descriptor'> -<meta name=variant content='?q=webgpu:api/validation/setBindGroup'> -<meta name=variant content='?q=webgpu:api/validation/setBlendColor'> -<meta name=variant content='?q=webgpu:api/validation/setScissorRect'> -<meta name=variant content='?q=webgpu:api/validation/setStencilReference'> -<meta name=variant content='?q=webgpu:api/validation/setViewport'> -<meta name=variant content='?q=webgpu:examples'> -<meta name=variant content='?q=webgpu:web-platform/canvas/context_creation'> -<meta name=variant content='?q=webgpu:web-platform/copyImageBitmapToTexture'> +<meta name=variant content='?q=webgpu:api,operation,buffers,create_mapped:*'> +<meta name=variant content='?q=webgpu:api,operation,buffers,map:*'> +<meta name=variant content='?q=webgpu:api,operation,buffers,map_detach:*'> +<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:*'> +<meta name=variant content='?q=webgpu:api,operation,command_buffer,basic:*'> +<meta name=variant content='?q=webgpu:api,operation,command_buffer,copies:*'> +<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,basic:*'> +<meta name=variant content='?q=webgpu:api,operation,fences:*'> +<meta name=variant content='?q=webgpu:api,operation,resource_init,copied_texture_clear:*'> +<meta name=variant content='?q=webgpu:api,validation,createBindGroup:*'> +<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:*'> +<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:*'> +<meta name=variant content='?q=webgpu:api,validation,createTexture:*'> +<meta name=variant content='?q=webgpu:api,validation,createView:*'> +<meta name=variant content='?q=webgpu:api,validation,error_scope:*'> +<meta name=variant content='?q=webgpu:api,validation,fences:*'> +<meta name=variant content='?q=webgpu:api,validation,queue_submit:*'> +<meta name=variant content='?q=webgpu:api,validation,render_pass_descriptor:*'> +<meta name=variant content='?q=webgpu:api,validation,setBindGroup:*'> +<meta name=variant content='?q=webgpu:api,validation,setBlendColor:*'> +<meta name=variant content='?q=webgpu:api,validation,setScissorRect:*'> +<meta name=variant content='?q=webgpu:api,validation,setStencilReference:*'> +<meta name=variant content='?q=webgpu:api,validation,setViewport:*'> +<meta name=variant content='?q=webgpu:examples:*'> +<meta name=variant content='?q=webgpu:web-platform,canvas,context_creation:*'> +<meta name=variant content='?q=webgpu:web-platform,copyImageBitmapToTexture:*'> diff --git a/webgpu/webgpu/api/operation/buffers/create_mapped.spec.js b/webgpu/webgpu/api/operation/buffers/create_mapped.spec.js index 7b2bc39..1c3b125 100644 --- a/webgpu/webgpu/api/operation/buffers/create_mapped.spec.js +++ b/webgpu/webgpu/api/operation/buffers/create_mapped.spec.js
@@ -2,18 +2,20 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -export const description = ``; -import { pbool, pcombine, poptions } from '../../../../common/framework/params.js'; -import { TestGroup } from '../../../../common/framework/test_group.js'; +export const description = ''; +import { params, pbool, poptions } from '../../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { MappingTest } from './mapping_test.js'; -export const g = new TestGroup(MappingTest); -g.test('createBufferMapped', async t => { - const size = t.params.size; +export const g = makeTestGroup(MappingTest); +g.test('createBufferMapped').params(params().combine(poptions('size', [12, 512 * 1024])).combine(pbool('mappable'))).fn(t => { + const { + size, + mappable + } = t.params; const [buffer, arrayBuffer] = t.device.createBufferMapped({ size, - usage: GPUBufferUsage.COPY_SRC | (t.params.mappable ? GPUBufferUsage.MAP_WRITE : 0) + usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0) }); t.checkMapWrite(buffer, arrayBuffer, size); -}).params(pcombine(poptions('size', [12, 512 * 1024]), // -pbool('mappable'))); +}); //# sourceMappingURL=create_mapped.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/operation/buffers/map.spec.js b/webgpu/webgpu/api/operation/buffers/map.spec.js index 1c73d62..842421d 100644 --- a/webgpu/webgpu/api/operation/buffers/map.spec.js +++ b/webgpu/webgpu/api/operation/buffers/map.spec.js
@@ -2,22 +2,26 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -export const description = ``; -import { pbool, pcombine, poptions } from '../../../../common/framework/params.js'; -import { TestGroup } from '../../../../common/framework/test_group.js'; +export const description = ''; +import { pbool, poptions, params } from '../../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { MappingTest } from './mapping_test.js'; -export const g = new TestGroup(MappingTest); -g.test('mapWriteAsync', async t => { - const size = t.params.size; +export const g = makeTestGroup(MappingTest); +g.test('mapWriteAsync').params(poptions('size', [12, 512 * 1024])).fn(async t => { + const { + size + } = t.params; const buffer = t.device.createBuffer({ size, usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE }); const arrayBuffer = await buffer.mapWriteAsync(); t.checkMapWrite(buffer, arrayBuffer, size); -}).params(poptions('size', [12, 512 * 1024])); -g.test('mapReadAsync', async t => { - const size = t.params.size; +}); +g.test('mapReadAsync').params(poptions('size', [12, 512 * 1024])).fn(async t => { + const { + size + } = t.params; const [buffer, init] = t.device.createBufferMapped({ size, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ @@ -32,14 +36,16 @@ buffer.unmap(); const actual = new Uint8Array((await buffer.mapReadAsync())); t.expectBuffer(actual, new Uint8Array(expected.buffer)); -}).params(poptions('size', [12, 512 * 1024])); -g.test('createBufferMapped', async t => { - const size = t.params.size; +}); +g.test('createBufferMapped').params(params().combine(poptions('size', [12, 512 * 1024])).combine(pbool('mappable'))).fn(async t => { + const { + size, + mappable + } = t.params; const [buffer, arrayBuffer] = t.device.createBufferMapped({ size, - usage: GPUBufferUsage.COPY_SRC | (t.params.mappable ? GPUBufferUsage.MAP_WRITE : 0) + usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0) }); t.checkMapWrite(buffer, arrayBuffer, size); -}).params(pcombine(poptions('size', [12, 512 * 1024]), // -pbool('mappable'))); +}); //# sourceMappingURL=map.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/operation/buffers/map_detach.spec.js b/webgpu/webgpu/api/operation/buffers/map_detach.spec.js index 14d5be5..8ae0be0 100644 --- a/webgpu/webgpu/api/operation/buffers/map_detach.spec.js +++ b/webgpu/webgpu/api/operation/buffers/map_detach.spec.js
@@ -2,8 +2,8 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -export const description = ``; -import { TestGroup } from '../../../../common/framework/test_group.js'; +export const description = ''; +import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; class F extends GPUTest { @@ -19,15 +19,26 @@ } -export const g = new TestGroup(F); -g.test('mapWriteAsync', async t => { +export const g = makeTestGroup(F); +g.test('mapWriteAsync').params([{ + unmap: true, + destroy: false +}, // +{ + unmap: false, + destroy: true +}, { + unmap: true, + destroy: true +}]).fn(async t => { const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_WRITE }); const arrayBuffer = await buffer.mapWriteAsync(); t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy); -}).params([{ +}); +g.test('mapReadAsync').params([{ unmap: true, destroy: false }, // @@ -37,26 +48,24 @@ }, { unmap: true, destroy: true -}]); -g.test('mapReadAsync', async t => { +}]).fn(async t => { const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_READ }); const arrayBuffer = await buffer.mapReadAsync(); t.checkDetach(buffer, arrayBuffer, t.params.unmap, t.params.destroy); -}).params([{ +}); +g.test('create_mapped').params([{ unmap: true, destroy: false -}, // -{ +}, { unmap: false, destroy: true }, { unmap: true, destroy: true -}]); -g.test('create mapped', async t => { +}]).fn(async t => { const desc = { size: 4, usage: GPUBufferUsage.MAP_WRITE @@ -69,14 +78,5 @@ if (t.params.destroy) buffer.destroy(); t.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached'); t.expect(view.byteLength === 0, 'ArrayBufferView should be detached'); -}).params([{ - unmap: true, - destroy: false -}, { - unmap: false, - destroy: true -}, { - unmap: true, - destroy: true -}]); +}); //# sourceMappingURL=map_detach.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/operation/buffers/map_oom.spec.js b/webgpu/webgpu/api/operation/buffers/map_oom.spec.js index ee642a5..a04347f 100644 --- a/webgpu/webgpu/api/operation/buffers/map_oom.spec.js +++ b/webgpu/webgpu/api/operation/buffers/map_oom.spec.js
@@ -2,8 +2,8 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -export const description = ``; -import { TestGroup } from '../../../../common/framework/test_group.js'; +export const description = ''; +import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; function getBufferDesc() { @@ -13,16 +13,16 @@ }; } -export const g = new TestGroup(GPUTest); -g.test('mapWriteAsync', async t => { +export const g = makeTestGroup(GPUTest); +g.test('mapWriteAsync').fn(async t => { const buffer = t.device.createBuffer(getBufferDesc()); t.shouldReject('RangeError', buffer.mapWriteAsync()); }); -g.test('mapReadAsync', async t => { +g.test('mapReadAsync').fn(async t => { const buffer = t.device.createBuffer(getBufferDesc()); t.shouldReject('RangeError', buffer.mapReadAsync()); }); -g.test('createBufferMapped', async t => { +g.test('createBufferMapped').fn(async t => { t.shouldThrow('RangeError', () => { t.device.createBufferMapped(getBufferDesc()); }); diff --git a/webgpu/webgpu/api/operation/command_buffer/basic.spec.js b/webgpu/webgpu/api/operation/command_buffer/basic.spec.js index bc4020b..547a086 100644 --- a/webgpu/webgpu/api/operation/command_buffer/basic.spec.js +++ b/webgpu/webgpu/api/operation/command_buffer/basic.spec.js
@@ -5,10 +5,10 @@ export const description = ` Basic tests. `; -import { TestGroup } from '../../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; -export const g = new TestGroup(GPUTest); -g.test('empty', async t => { +export const g = makeTestGroup(GPUTest); +g.test('empty').fn(async t => { const encoder = t.device.createCommandEncoder(); const cmd = encoder.finish(); t.device.defaultQueue.submit([cmd]); diff --git a/webgpu/webgpu/api/operation/command_buffer/copies.spec.js b/webgpu/webgpu/api/operation/command_buffer/copies.spec.js index c330415..cc2ea6d 100644 --- a/webgpu/webgpu/api/operation/command_buffer/copies.spec.js +++ b/webgpu/webgpu/api/operation/command_buffer/copies.spec.js
@@ -5,10 +5,10 @@ export const description = ` copy{Buffer,Texture}To{Buffer,Texture} tests. `; -import { TestGroup } from '../../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { GPUTest } from '../../../gpu_test.js'; -export const g = new TestGroup(GPUTest); -g.test('b2b', async t => { +export const g = makeTestGroup(GPUTest); +g.test('b2b').fn(async t => { const data = new Uint32Array([0x01020304]); const [src, map] = t.device.createBufferMapped({ size: 4, @@ -25,7 +25,7 @@ t.device.defaultQueue.submit([encoder.finish()]); t.expectContents(dst, data); }); -g.test('b2t2b', async t => { +g.test('b2t2b').fn(async t => { const data = new Uint32Array([0x01020304]); const [src, map] = t.device.createBufferMapped({ size: 4, @@ -82,7 +82,7 @@ t.device.defaultQueue.submit([encoder.finish()]); t.expectContents(dst, data); }); -g.test('b2t2t2b', async t => { +g.test('b2t2t2b').fn(async t => { const data = new Uint32Array([0x01020304]); const [src, map] = t.device.createBufferMapped({ size: 4, diff --git a/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js b/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js index 88710e8..815eb5b 100644 --- a/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js +++ b/webgpu/webgpu/api/operation/command_buffer/render/basic.spec.js
@@ -5,10 +5,10 @@ export const description = ` Basic command buffer rendering tests. `; -import { TestGroup } from '../../../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { GPUTest } from '../../../../gpu_test.js'; -export const g = new TestGroup(GPUTest); -g.test('clear', async t => { +export const g = makeTestGroup(GPUTest); +g.test('clear').fn(async t => { const dst = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST diff --git a/webgpu/webgpu/api/operation/fences.spec.js b/webgpu/webgpu/api/operation/fences.spec.js index 386530e..f9f079b 100644 --- a/webgpu/webgpu/api/operation/fences.spec.js +++ b/webgpu/webgpu/api/operation/fences.spec.js
@@ -2,42 +2,42 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -export const description = ``; -import { attemptGarbageCollection } from '../../../common/framework/collect_garbage.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; +export const description = ''; +import { makeTestGroup } from '../../../common/framework/test_group.js'; +import { attemptGarbageCollection } from '../../../common/framework/util/collect_garbage.js'; import { raceWithRejectOnTimeout } from '../../../common/framework/util/util.js'; import { GPUTest } from '../../gpu_test.js'; -export const g = new TestGroup(GPUTest); -g.test('initial/no descriptor', t => { +export const g = makeTestGroup(GPUTest); +g.test('initial,no_descriptor').fn(t => { const fence = t.queue.createFence(); t.expect(fence.getCompletedValue() === 0); }); -g.test('initial/empty descriptor', t => { +g.test('initial,empty_descriptor').fn(t => { const fence = t.queue.createFence({}); t.expect(fence.getCompletedValue() === 0); }); -g.test('initial/descriptor with initialValue', t => { +g.test('initial,descriptor_with_initialValue').fn(t => { const fence = t.queue.createFence({ initialValue: 2 }); t.expect(fence.getCompletedValue() === 2); }); // Promise resolves when onCompletion value is less than signal value. -g.test('wait/less than signaled', async t => { +g.test('wait,less_than_signaled').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 2); await fence.onCompletion(1); t.expect(fence.getCompletedValue() === 2); }); // Promise resolves when onCompletion value is equal to signal value. -g.test('wait/equal to signaled', async t => { +g.test('wait,equal_to_signaled').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 2); await fence.onCompletion(2); t.expect(fence.getCompletedValue() === 2); }); // All promises resolve when signal is called once. -g.test('wait/signaled once', async t => { +g.test('wait,signaled_once').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 20); const promises = []; @@ -51,7 +51,7 @@ await Promise.all(promises); }); // Promise resolves when signal is called multiple times. -g.test('wait/signaled multiple times', async t => { +g.test('wait,signaled_multiple_times').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 1); t.queue.signal(fence, 2); @@ -59,7 +59,7 @@ t.expect(fence.getCompletedValue() === 2); }); // Promise resolves if fence has already completed. -g.test('wait/already completed', async t => { +g.test('wait,already_completed').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 2); // Wait for value to update. @@ -74,7 +74,7 @@ t.expect(fence.getCompletedValue() === 2); }); // Test many calls to signal and wait on fence values one at a time. -g.test('wait/many/serially', async t => { +g.test('wait,many,serially').fn(async t => { const fence = t.queue.createFence(); for (let i = 1; i <= 20; ++i) { @@ -84,7 +84,7 @@ } }); // Test many calls to signal and wait on all fence values. -g.test('wait/many/parallel', async t => { +g.test('wait,many,parallel').fn(async t => { const fence = t.queue.createFence(); const promises = []; @@ -99,7 +99,7 @@ t.expect(fence.getCompletedValue() === 20); }); // Test onCompletion promise resolves within a time limit. -g.test('wait/resolves within timeout', t => { +g.test('wait,resolves_within_timeout').fn(t => { const fence = t.queue.createFence(); t.queue.signal(fence, 2); return raceWithRejectOnTimeout((async () => { @@ -108,23 +108,23 @@ })(), 100, 'The fence has not been resolved within time limit.'); }); // Test dropping references to the fence and onCompletion promise does not crash. -g.test('drop/fence and promise', t => { +g.test('drop,fence_and_promise').fn(async t => { { const fence = t.queue.createFence(); t.queue.signal(fence, 2); fence.onCompletion(2); } - attemptGarbageCollection(); + await attemptGarbageCollection(); }); // Test dropping references to the fence and holding the promise does not crash. -g.test('drop/promise', async t => { +g.test('drop,promise').fn(async t => { let promise; { const fence = t.queue.createFence(); t.queue.signal(fence, 2); promise = fence.onCompletion(2); } - attemptGarbageCollection(); + await attemptGarbageCollection(); await promise; }); //# sourceMappingURL=fences.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/operation/resource_init/copied_texture_clear.spec.js b/webgpu/webgpu/api/operation/resource_init/copied_texture_clear.spec.js new file mode 100644 index 0000000..22a71bf --- /dev/null +++ b/webgpu/webgpu/api/operation/resource_init/copied_texture_clear.spec.js
@@ -0,0 +1,86 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +export const description = 'Test uninitialized textures are initialized to zero when copied.'; +import * as C from '../../../../common/constants.js'; +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { assert, unreachable } from '../../../../common/framework/util/util.js'; +import { ReadMethod, TextureZeroInitTest } from './texture_zero_init_test.js'; + +class CopiedTextureClearTest extends TextureZeroInitTest { + checkContentsByBufferCopy(texture, state, subresourceRange) { + for (const { + level: mipLevel, + slice + } of subresourceRange.each()) { + assert(this.params.dimension === '2d'); + this.expectSingleColor(texture, this.params.format, { + size: [this.textureWidth, this.textureHeight, 1], + dimension: this.params.dimension, + slice, + layout: { + mipLevel + }, + exp: this.stateToTexelComponents[state] + }); + } + } + + checkContentsByTextureCopy(texture, state, subresourceRange) { + for (const { + level, + slice + } of subresourceRange.each()) { + assert(this.params.dimension === '2d'); + const width = this.textureWidth >> level; + const height = this.textureHeight >> level; + const dst = this.device.createTexture({ + size: [width, height, 1], + format: this.params.format, + usage: C.TextureUsage.CopyDst | C.TextureUsage.CopySrc + }); + const commandEncoder = this.device.createCommandEncoder(); + commandEncoder.copyTextureToTexture({ + texture, + mipLevel: level, + arrayLayer: slice + }, { + texture: dst, + mipLevel: 0, + arrayLayer: 0 + }, { + width, + height, + depth: 1 + }); + this.queue.submit([commandEncoder.finish()]); + this.expectSingleColor(dst, this.params.format, { + size: [width, height, 1], + exp: this.stateToTexelComponents[state] + }); + } + } + + checkContents(texture, state, subresourceRange) { + switch (this.params.readMethod) { + case ReadMethod.CopyToBuffer: + this.checkContentsByBufferCopy(texture, state, subresourceRange); + break; + + case ReadMethod.CopyToTexture: + this.checkContentsByTextureCopy(texture, state, subresourceRange); + break; + + default: + unreachable(); + } + } + +} + +export const g = makeTestGroup(CopiedTextureClearTest); +g.test('uninitialized_texture_is_zero').params(TextureZeroInitTest.generateParams([ReadMethod.CopyToBuffer, ReadMethod.CopyToTexture])).fn(t => { + t.run(); +}); +//# sourceMappingURL=copied_texture_clear.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/operation/resource_init/texture_zero_init_test.js b/webgpu/webgpu/api/operation/resource_init/texture_zero_init_test.js new file mode 100644 index 0000000..5dc3c22 --- /dev/null +++ b/webgpu/webgpu/api/operation/resource_init/texture_zero_init_test.js
@@ -0,0 +1,563 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import * as C from '../../../../common/constants.js'; +import { params, poptions, pbool } from '../../../../common/framework/params_builder.js'; +import { assert, unreachable } from '../../../../common/framework/util/util.js'; +import { kTextureAspects, kTextureFormatInfo, kTextureFormats } from '../../../capability_info.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { createTextureUploadBuffer } from '../../../util/texture/layout.js'; +import { SubresourceRange } from '../../../util/texture/subresource.js'; +import { getTexelDataRepresentation } from '../../../util/texture/texelData.js'; +var UninitializeMethod; + +(function (UninitializeMethod) { + UninitializeMethod["Creation"] = "Creation"; + UninitializeMethod["StoreOpClear"] = "StoreOpClear"; +})(UninitializeMethod || (UninitializeMethod = {})); + +const kUninitializeMethods = Object.keys(UninitializeMethod); +export let ReadMethod; + +(function (ReadMethod) { + ReadMethod["Sample"] = "Sample"; + ReadMethod["CopyToBuffer"] = "CopyToBuffer"; + ReadMethod["CopyToTexture"] = "CopyToTexture"; + ReadMethod["DepthTest"] = "DepthTest"; + ReadMethod["StencilTest"] = "StencilTest"; + ReadMethod["ColorBlending"] = "ColorBlending"; + ReadMethod["Storage"] = "Storage"; +})(ReadMethod || (ReadMethod = {})); + +const kMipLevelCounts = [1, 5]; // For each mip level count, define the mip ranges to leave uninitialized. + +const kUninitializedMipRangesToTest = { + 1: [{ + begin: 0, + end: 1 + }], + // Test the only mip + 5: [{ + begin: 0, + end: 2 + }, { + begin: 3, + end: 4 + }] // Test a range and a single mip + +}; // Test with these sample counts. + +const kSampleCounts = [1, 4]; // Test with these slice counts. This means the depth of a 3d texture or the number +// or layers in a 2D or a 1D texture array. + +// For each slice count, define the slices to leave uninitialized. +const kUninitializedSliceRangesToTest = { + 1: [{ + begin: 0, + end: 1 + }], + // Test the only slice + 7: [{ + begin: 2, + end: 4 + }, { + begin: 6, + end: 7 + }] // Test a range and a single slice + +}; // Test with these combinations of texture dimension and sliceCount. + +const kCreationSizes = [// { dimension: '1d', sliceCount: 7 }, // TODO: 1d textures +{ + dimension: '2d', + sliceCount: 1 +}, // 2d textures +{ + dimension: '2d', + sliceCount: 7 +} // 2d array textures +// { dimension: '3d', sliceCount: 7 }, // TODO: 3d textures +]; // Enums to abstract over color / depth / stencil values in textures. Depending on the texture format, +// the data for each value may have a different representation. These enums are converted to a +// representation such that their values can be compared. ex.) An integer is needed to upload to an +// unsigned normalized format, but its value is read as a float in the shader. + +export let InitializedState; + +(function (InitializedState) { + InitializedState[InitializedState["Canary"] = 0] = "Canary"; + InitializedState[InitializedState["Zero"] = 1] = "Zero"; +})(InitializedState || (InitializedState = {})); + +export function initializedStateAsFloat(state) { + switch (state) { + case InitializedState.Zero: + return 0; + + case InitializedState.Canary: + return 1; + + default: + unreachable(); + } +} +export function initializedStateAsUint(state) { + switch (state) { + case InitializedState.Zero: + return 0; + + case InitializedState.Canary: + return 255; + + default: + unreachable(); + } +} +export function initializedStateAsSint(state) { + switch (state) { + case InitializedState.Zero: + return 0; + + case InitializedState.Canary: + return -1; + + default: + unreachable(); + } +} +export function initializedStateAsColor(state, format) { + let value; + + if (format.indexOf('uint') !== -1) { + value = initializedStateAsUint(state); + } else if (format.indexOf('sint') !== -1) { + value = initializedStateAsSint(state); + } else { + value = initializedStateAsFloat(state); + } + + return [value, value, value, value]; +} +export function initializedStateAsDepth(state) { + switch (state) { + case InitializedState.Zero: + return 0; + + case InitializedState.Canary: + return 1; + + default: + unreachable(); + } +} +export function initializedStateAsStencil(state) { + switch (state) { + case InitializedState.Zero: + return 0; + + case InitializedState.Canary: + return 42; + + default: + unreachable(); + } +} + +function getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod) { + let usage = C.TextureUsage.CopyDst; + + switch (uninitializeMethod) { + case UninitializeMethod.Creation: + break; + + case UninitializeMethod.StoreOpClear: + usage |= C.TextureUsage.OutputAttachment; + break; + + default: + unreachable(); + } + + switch (readMethod) { + case ReadMethod.CopyToBuffer: + case ReadMethod.CopyToTexture: + usage |= C.TextureUsage.CopySrc; + break; + + case ReadMethod.Sample: + usage |= C.TextureUsage.Sampled; + break; + + case ReadMethod.Storage: + usage |= C.TextureUsage.Storage; + break; + + case ReadMethod.DepthTest: + case ReadMethod.StencilTest: + case ReadMethod.ColorBlending: + usage |= C.TextureUsage.OutputAttachment; + break; + + default: + unreachable(); + } + + if (sampleCount > 1) { + // Copies to multisampled textures are not allowed. We need OutputAttachment to initialize + // canary data in multisampled textures. + usage |= C.TextureUsage.OutputAttachment; + } + + if (!kTextureFormatInfo[format].copyable) { + // Copies are not possible. We need OutputAttachment to initialize + // canary data. + assert(kTextureFormatInfo[format].renderable); + usage |= C.TextureUsage.OutputAttachment; + } + + return usage; +} + +export class TextureZeroInitTest extends GPUTest { + constructor(rec, params) { + super(rec, params); + + _defineProperty(this, "stateToTexelComponents", void 0); + + const stateToTexelComponents = state => { + const [R, G, B, A] = initializedStateAsColor(state, this.params.format); + return { + R, + G, + B, + A, + Depth: initializedStateAsDepth(state), + Stencil: initializedStateAsStencil(state) + }; + }; + + this.stateToTexelComponents = { + [InitializedState.Zero]: stateToTexelComponents(InitializedState.Zero), + [InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary) + }; + } + + get params() { + return super.params; + } + + get textureWidth() { + let width = 1 << this.params.mipLevelCount; + + if (this.params.nonPowerOfTwo) { + width = 2 * width - 1; + } + + return width; + } + + get textureHeight() { + let height = 1 << this.params.mipLevelCount; + + if (this.params.nonPowerOfTwo) { + height = 2 * height - 1; + } + + return height; + } // Used to iterate subresources and check that their uninitialized contents are zero when accessed + + + *iterateUninitializedSubresources() { + for (const mipRange of kUninitializedMipRangesToTest[this.params.mipLevelCount]) { + for (const sliceRange of kUninitializedSliceRangesToTest[this.params.sliceCount]) { + yield new SubresourceRange({ + mipRange, + sliceRange + }); + } + } + } // Used to iterate and initialize other subresources not checked for zero-initialization. + // Zero-initialization of uninitialized subresources should not have side effects on already + // initialized subresources. + + + *iterateInitializedSubresources() { + const uninitialized = new Array(this.params.mipLevelCount); + + for (let level = 0; level < uninitialized.length; ++level) { + uninitialized[level] = new Array(this.params.sliceCount); + } + + for (const subresources of this.iterateUninitializedSubresources()) { + for (const { + level, + slice + } of subresources.each()) { + uninitialized[level][slice] = true; + } + } + + for (let level = 0; level < uninitialized.length; ++level) { + for (let slice = 0; slice < uninitialized[level].length; ++slice) { + if (!uninitialized[level][slice]) { + yield new SubresourceRange({ + mipRange: { + begin: level, + count: 1 + }, + sliceRange: { + begin: slice, + count: 1 + } + }); + } + } + } + } + + *generateTextureViewDescriptorsForRendering(aspect, subresourceRange) { + const viewDescriptor = { + dimension: '2d', + aspect + }; + + if (subresourceRange === undefined) { + return viewDescriptor; + } + + for (const { + level, + slice + } of subresourceRange.each()) { + yield { ...viewDescriptor, + baseMipLevel: level, + mipLevelCount: 1, + baseArrayLayer: slice, + arrayLayerCount: 1 + }; + } + } + + initializeWithStoreOp(state, texture, subresourceRange) { + const commandEncoder = this.device.createCommandEncoder(); + + for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(this.params.aspect, subresourceRange)) { + if (kTextureFormatInfo[this.params.format].color) { + commandEncoder.beginRenderPass({ + colorAttachments: [{ + attachment: texture.createView(viewDescriptor), + storeOp: 'store', + loadValue: initializedStateAsColor(state, this.params.format) + }] + }).endPass(); + } else { + commandEncoder.beginRenderPass({ + colorAttachments: [], + depthStencilAttachment: { + attachment: texture.createView(viewDescriptor), + depthStoreOp: 'store', + depthLoadValue: initializedStateAsDepth(state), + stencilStoreOp: 'store', + stencilLoadValue: initializedStateAsStencil(state) + } + }).endPass(); + } + } + + this.queue.submit([commandEncoder.finish()]); + } + + initializeWithCopy(texture, state, subresourceRange) { + if (this.params.dimension === '1d' || this.params.dimension === '3d') { + // TODO: https://github.com/gpuweb/gpuweb/issues/69 + // Copies with 1D and 3D textures are not yet specified + unreachable(); + } + + const firstSubresource = subresourceRange.each().next().value; + assert(typeof firstSubresource !== 'undefined'); + const largestWidth = this.textureWidth >> firstSubresource.level; + const largestHeight = this.textureHeight >> firstSubresource.level; + const texelData = new Uint8Array(getTexelDataRepresentation(this.params.format).getBytes(this.stateToTexelComponents[state])); + const { + buffer, + bytesPerRow, + rowsPerImage + } = createTextureUploadBuffer(texelData, this.device, this.params.format, this.params.dimension, [largestWidth, largestHeight, 1]); + const commandEncoder = this.device.createCommandEncoder(); + + for (const { + level, + slice + } of subresourceRange.each()) { + const width = this.textureWidth >> level; + const height = this.textureHeight >> level; + commandEncoder.copyBufferToTexture({ + buffer, + bytesPerRow, + rowsPerImage + }, { + texture, + mipLevel: level, + arrayLayer: slice + }, { + width, + height, + depth: 1 + }); + } + + this.queue.submit([commandEncoder.finish()]); + buffer.destroy(); + } + + initializeTexture(texture, state, subresourceRange) { + if (this.params.sampleCount > 1 || !kTextureFormatInfo[this.params.format].copyable) { + // Copies to multisampled textures not yet specified. + // Use a storeOp for now. + assert(kTextureFormatInfo[this.params.format].renderable); + this.initializeWithStoreOp(state, texture, subresourceRange); + } else { + this.initializeWithCopy(texture, state, subresourceRange); + } + } + + discardTexture(texture, subresourceRange) { + const commandEncoder = this.device.createCommandEncoder(); + + for (const desc of this.generateTextureViewDescriptorsForRendering(this.params.aspect, subresourceRange)) { + if (kTextureFormatInfo[this.params.format].color) { + commandEncoder.beginRenderPass({ + colorAttachments: [{ + attachment: texture.createView(desc), + storeOp: 'clear', + loadValue: 'load' + }] + }).endPass(); + } else { + commandEncoder.beginRenderPass({ + colorAttachments: [], + depthStencilAttachment: { + attachment: texture.createView(desc), + depthStoreOp: 'clear', + depthLoadValue: 'load', + stencilStoreOp: 'clear', + stencilLoadValue: 'load' + } + }).endPass(); + } + } + + this.queue.submit([commandEncoder.finish()]); + } + + static generateParams(readMethods) { + return (// TODO: Consider making a list of "valid" texture descriptors in capability_info. + params().combine(poptions('format', kTextureFormats)).combine(poptions('aspect', kTextureAspects)).unless(({ + format, + aspect + }) => aspect === 'depth-only' && !kTextureFormatInfo[format].depth || aspect === 'stencil-only' && !kTextureFormatInfo[format].stencil).combine(poptions('mipLevelCount', kMipLevelCounts)).combine(poptions('sampleCount', kSampleCounts)) // Multisampled textures may only have one mip + .unless(({ + sampleCount, + mipLevelCount + }) => sampleCount > 1 && mipLevelCount > 1).combine(poptions('uninitializeMethod', kUninitializeMethods)).combine(poptions('readMethod', readMethods)).unless(({ + readMethod, + format + }) => // It doesn't make sense to copy from a packed depth format. + // This is not specified yet, but it will probably be disallowed as the bits may + // be vendor-specific. + // TODO: Test copying out of the stencil aspect. + (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) && (format === 'depth24plus' || format === 'depth24plus-stencil8')).unless(({ + readMethod, + format + }) => readMethod === ReadMethod.DepthTest && !kTextureFormatInfo[format].depth || readMethod === ReadMethod.StencilTest && !kTextureFormatInfo[format].stencil || readMethod === ReadMethod.ColorBlending && !kTextureFormatInfo[format].color || // TODO: Test with depth sampling + readMethod === ReadMethod.Sample && kTextureFormatInfo[format].depth).unless(({ + readMethod, + sampleCount + }) => // We can only read from multisampled textures by sampling. + sampleCount > 1 && (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)).combine(kCreationSizes) // Multisampled 3D / 2D array textures not supported. + .unless(({ + sampleCount, + sliceCount + }) => sampleCount > 1 && sliceCount > 1).filter(({ + format, + sampleCount, + uninitializeMethod, + readMethod + }) => { + const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod); + + if (usage & C.TextureUsage.OutputAttachment && !kTextureFormatInfo[format].renderable) { + return false; + } + + if (usage & C.TextureUsage.Storage && !kTextureFormatInfo[format].storage) { + return false; + } + + return true; + }).combine(pbool('nonPowerOfTwo')) + ); + } + + run() { + const { + format, + dimension, + mipLevelCount, + sliceCount, + sampleCount, + uninitializeMethod, + readMethod + } = this.params; + const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod); + const texture = this.device.createTexture({ + size: [this.textureWidth, this.textureHeight, sliceCount], + format, + dimension, + usage, + mipLevelCount, + sampleCount + }); // Initialize some subresources with canary values + + for (const subresourceRange of this.iterateInitializedSubresources()) { + this.initializeTexture(texture, InitializedState.Canary, subresourceRange); + } + + switch (uninitializeMethod) { + case UninitializeMethod.Creation: + break; + + case UninitializeMethod.StoreOpClear: + // Initialize the rest of the resources. + for (const subresourceRange of this.iterateUninitializedSubresources()) { + this.initializeTexture(texture, InitializedState.Canary, subresourceRange); + } // Then use a store op to discard their contents. + + + for (const subresourceRange of this.iterateUninitializedSubresources()) { + this.discardTexture(texture, subresourceRange); + } + + break; + + default: + unreachable(); + } // Check that all uninitialized resources are zero. + + + for (const subresourceRange of this.iterateUninitializedSubresources()) { + this.checkContents(texture, InitializedState.Zero, subresourceRange); + } // Check the all other resources are unchanged. + + + for (const subresourceRange of this.iterateInitializedSubresources()) { + this.checkContents(texture, InitializedState.Canary, subresourceRange); + } + } + +} +//# sourceMappingURL=texture_zero_init_test.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/createBindGroup.spec.js b/webgpu/webgpu/api/validation/createBindGroup.spec.js index 97683cf..1eb8f95 100644 --- a/webgpu/webgpu/api/validation/createBindGroup.spec.js +++ b/webgpu/webgpu/api/validation/createBindGroup.spec.js
@@ -6,18 +6,18 @@ createBindGroup validation tests. `; import * as C from '../../../common/constants.js'; -import { pcombine, poptions } from '../../../common/framework/params.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { poptions, params } from '../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { unreachable } from '../../../common/framework/util/util.js'; -import { kBindingTypes } from '../../capability_info.js'; -import { BindingResourceType, ValidationTest, resourceBindingMatches } from './validation_test.js'; +import { kBindingTypes, kBindingTypeInfo, kBindableResources, kTextureUsages, kTextureBindingTypes, kTextureBindingTypeInfo } from '../../capability_info.js'; +import { ValidationTest } from './validation_test.js'; function clone(descriptor) { return JSON.parse(JSON.stringify(descriptor)); } -export const g = new TestGroup(ValidationTest); -g.test('binding count mismatch', async t => { +export const g = makeTestGroup(ValidationTest); +g.test('binding_count_mismatch').fn(async t => { const bindGroupLayout = t.device.createBindGroupLayout({ entries: [{ binding: 0, @@ -56,7 +56,7 @@ t.device.createBindGroup(badDescriptor); }); }); -g.test('binding must be present in layout', async t => { +g.test('binding_must_be_present_in_layout').fn(async t => { const bindGroupLayout = t.device.createBindGroupLayout({ entries: [{ binding: 0, @@ -89,18 +89,23 @@ t.device.createBindGroup(badDescriptor); }); }); -g.test('buffer binding must contain exactly one buffer of its type', t => { - const bindingType = t.params.bindingType; - const resourceType = t.params.resourceType; +g.test('buffer_binding_must_contain_exactly_one_buffer_of_its_type').params(params().combine(poptions('bindingType', kBindingTypes)).combine(poptions('resourceType', kBindableResources))).fn(t => { + const { + bindingType, + resourceType + } = t.params; + const info = kBindingTypeInfo[bindingType]; + const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined; const layout = t.device.createBindGroupLayout({ entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, - type: bindingType + type: bindingType, + storageTextureFormat }] }); const resource = t.getBindingResource(resourceType); - const shouldError = !resourceBindingMatches(bindingType, resourceType); + const resourceBindingMatches = info.resource === resourceType; t.expectValidationError(() => { t.device.createBindGroup({ layout, @@ -109,73 +114,44 @@ resource }] }); - }, shouldError); -}).params(pcombine(poptions('bindingType', kBindingTypes), poptions('resourceType', Object.keys(BindingResourceType)))); -g.test('texture binding must have correct usage', async t => { - const type = t.params.type; - const usage = t.params._usage; + }, !resourceBindingMatches); +}); +g.test('texture_binding_must_have_correct_usage').params(params().combine(poptions('type', kTextureBindingTypes)).combine(poptions('usage', kTextureUsages))).fn(async t => { + const { + type, + usage + } = t.params; + const info = kTextureBindingTypeInfo[type]; + const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined; const bindGroupLayout = t.device.createBindGroupLayout({ entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, - type + type, + storageTextureFormat }] }); - const goodDescriptor = { + const descriptor = { size: { width: 16, height: 16, depth: 1 }, - format: C.TextureFormat.R8Unorm, + format: C.TextureFormat.RGBA8Unorm, usage - }; // Control case - - t.device.createBindGroup({ - entries: [{ - binding: 0, - resource: t.device.createTexture(goodDescriptor).createView() - }], - layout: bindGroupLayout - }); - - function* mismatchedTextureUsages() { - yield GPUTextureUsage.COPY_SRC; - yield GPUTextureUsage.COPY_DST; - - if (type !== 'sampled-texture') { - yield GPUTextureUsage.SAMPLED; - } - - if (type !== 'readonly-storage-texture' && type !== 'writeonly-storage-texture') { - yield GPUTextureUsage.STORAGE; - } - - yield GPUTextureUsage.OUTPUT_ATTACHMENT; - } // Mismatched texture binding usages are not valid. - - - for (const mismatchedTextureUsage of mismatchedTextureUsages()) { - const badDescriptor = clone(goodDescriptor); - badDescriptor.usage = mismatchedTextureUsage; - t.expectValidationError(() => { - t.device.createBindGroup({ - entries: [{ - binding: 0, - resource: t.device.createTexture(badDescriptor).createView() - }], - layout: bindGroupLayout - }); + }; + const shouldError = usage !== info.usage; + t.expectValidationError(() => { + t.device.createBindGroup({ + entries: [{ + binding: 0, + resource: t.device.createTexture(descriptor).createView() + }], + layout: bindGroupLayout }); - } -}).params([{ - type: 'sampled-texture', - _usage: C.TextureUsage.Sampled -}, { - type: 'storage-texture', - _usage: C.TextureUsage.Storage -}]); -g.test('texture must have correct component type', async t => { + }, shouldError); +}); +g.test('texture_must_have_correct_component_type').params(poptions('textureComponentType', [C.TextureComponentType.Float, C.TextureComponentType.Sint, C.TextureComponentType.Uint])).fn(async t => { const { textureComponentType } = t.params; @@ -246,9 +222,9 @@ }); }); } -}).params(poptions('textureComponentType', ['float', 'sint', 'uint'])); // TODO: Write test for all dimensions. +}); // TODO: Write test for all dimensions. -g.test('texture must have correct dimension', async t => { +g.test('texture_must_have_correct_dimension').fn(async t => { const bindGroupLayout = t.device.createBindGroupLayout({ entries: [{ binding: 0, @@ -287,45 +263,7 @@ }); }); }); -g.test('buffer offset and size for bind groups match', async t => { - const { - offset, - size, - _success - } = t.params; - const bindGroupLayout = t.device.createBindGroupLayout({ - entries: [{ - binding: 0, - visibility: GPUShaderStage.COMPUTE, - type: 'storage-buffer' - }] - }); - const buffer = t.device.createBuffer({ - size: 1024, - usage: GPUBufferUsage.STORAGE - }); - const descriptor = { - entries: [{ - binding: 0, - resource: { - buffer, - offset, - size - } - }], - layout: bindGroupLayout - }; - - if (_success) { - // Control case - t.device.createBindGroup(descriptor); - } else { - // Buffer offset and/or size don't match in bind groups. - t.expectValidationError(() => { - t.device.createBindGroup(descriptor); - }); - } -}).params([{ +g.test('buffer_offset_and_size_for_bind_groups_match').params([{ offset: 0, size: 512, _success: true @@ -402,5 +340,43 @@ size: 1, _success: false } // offset+size is OOB -]); +]).fn(async t => { + const { + offset, + size, + _success + } = t.params; + const bindGroupLayout = t.device.createBindGroupLayout({ + entries: [{ + binding: 0, + visibility: GPUShaderStage.COMPUTE, + type: 'storage-buffer' + }] + }); + const buffer = t.device.createBuffer({ + size: 1024, + usage: GPUBufferUsage.STORAGE + }); + const descriptor = { + entries: [{ + binding: 0, + resource: { + buffer, + offset, + size + } + }], + layout: bindGroupLayout + }; + + if (_success) { + // Control case + t.device.createBindGroup(descriptor); + } else { + // Buffer offset and/or size don't match in bind groups. + t.expectValidationError(() => { + t.device.createBindGroup(descriptor); + }); + } +}); //# sourceMappingURL=createBindGroup.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js b/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js index b03e015..3961ba3 100644 --- a/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js +++ b/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js
@@ -6,17 +6,17 @@ createBindGroupLayout validation tests. `; import * as C from '../../../common/constants.js'; -import { poptions } from '../../../common/framework/params.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; -import { kBindingTypeInfo, kBindingTypes, kMaxBindingsPerBindGroup, kPerStageBindingLimits, kShaderStages } from '../../capability_info.js'; +import { poptions, params } from '../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; +import { kBindingTypeInfo, kBindingTypes, kMaxBindingsPerBindGroup, kShaderStages } from '../../capability_info.js'; import { ValidationTest } from './validation_test.js'; function clone(descriptor) { return JSON.parse(JSON.stringify(descriptor)); } -export const g = new TestGroup(ValidationTest); -g.test('some binding index was specified more than once', async t => { +export const g = makeTestGroup(ValidationTest); +g.test('some_binding_index_was_specified_more_than_once').fn(async t => { const goodDescriptor = { entries: [{ binding: 0, @@ -37,7 +37,7 @@ t.device.createBindGroupLayout(badDescriptor); }); }); -g.test('Visibility of bindings can be 0', async t => { +g.test('visibility_of_bindings_can_be_0').fn(async t => { t.device.createBindGroupLayout({ entries: [{ binding: 0, @@ -46,7 +46,13 @@ }] }); }); -g.test('number of dynamic buffers exceeds the maximum value', async t => { +g.test('number_of_dynamic_buffers_exceeds_the_maximum_value').params([{ + type: C.BindingType.StorageBuffer, + maxDynamicBufferCount: 4 +}, { + type: C.BindingType.UniformBuffer, + maxDynamicBufferCount: 8 +}]).fn(async t => { const { type, maxDynamicBufferCount @@ -78,16 +84,12 @@ t.expectValidationError(() => { t.device.createBindGroupLayout(badDescriptor); }); -}).params([{ - type: C.BindingType.StorageBuffer, - maxDynamicBufferCount: 4 -}, { - type: C.BindingType.UniformBuffer, - maxDynamicBufferCount: 8 -}]); -g.test('dynamic set to true is allowed only for buffers', async t => { - const type = t.params.type; - const success = kBindingTypeInfo[type].type === 'buffer'; +}); +g.test('dynamic_set_to_true_is_allowed_only_for_buffers').params(poptions('type', kBindingTypes)).fn(async t => { + const { + type + } = t.params; + const success = kBindingTypeInfo[type].perPipelineLimitClass.maxDynamic > 0; const descriptor = { entries: [{ binding: 0, @@ -99,71 +101,53 @@ t.expectValidationError(() => { t.device.createBindGroupLayout(descriptor); }, !success); -}).params(poptions('type', kBindingTypes)); -let kCasesForMaxResourcesPerStageTests; -{ - // One bind group layout will be filled with kPerStageBindingLimit[...] of the type |type|. - // For each item in the array returned here, a case will be generated which tests a pipeline - // layout with one extra bind group layout with one extra binding. That extra binding will have: - // - // - If extraTypeSame, any of the binding types which counts toward the same limit as |type|. - // (i.e. 'storage-buffer' <-> 'readonly-storage-buffer'). - // - Otherwise, an arbitrary other type. - function* pickExtraBindingTypes(bindingType, extraTypeSame) { - const info = kBindingTypeInfo[bindingType]; +}); // One bind group layout will be filled with kPerStageBindingLimit[...] of the type |type|. +// For each item in the array returned here, a case will be generated which tests a pipeline +// layout with one extra bind group layout with one extra binding. That extra binding will have: +// +// - If extraTypeSame, any of the binding types which counts toward the same limit as |type|. +// (i.e. 'storage-buffer' <-> 'readonly-storage-buffer'). +// - Otherwise, an arbitrary other type. - if (extraTypeSame) { - for (const extraBindingType of kBindingTypes) { - if (info.perStageLimitType === kBindingTypeInfo[extraBindingType].perStageLimitType) { - yield extraBindingType; - } - } - } else { - yield info.perStageLimitType === 'sampler' ? 'sampled-texture' : 'sampler'; - } - } +function* pickExtraBindingTypes(bindingType, extraTypeSame) { + const info = kBindingTypeInfo[bindingType]; - kCasesForMaxResourcesPerStageTests = []; - - for (const maxedType of kBindingTypes) { - for (const maxedVisibility of kShaderStages) { - // Don't generate a case where maxedType isn't valid in maxedVisibility. - if (!(kBindingTypeInfo[maxedType].validStages & maxedVisibility)) continue; - - for (const extraTypeSame of [true, false]) { - for (const extraType of pickExtraBindingTypes(maxedType, extraTypeSame)) { - for (const extraVisibility of kShaderStages) { - // Don't generate a case where extraType isn't valid in extraVisibility. - if (!(kBindingTypeInfo[extraType].validStages & extraVisibility)) continue; - kCasesForMaxResourcesPerStageTests.push({ - maxedType, - maxedVisibility, - extraType, - extraVisibility - }); - } - } + if (extraTypeSame) { + for (const extraBindingType of kBindingTypes) { + if (info.perStageLimitClass.class === kBindingTypeInfo[extraBindingType].perStageLimitClass.class) { + yield extraBindingType; } } + } else { + yield info.perStageLimitClass.class === 'sampler' ? 'sampled-texture' : 'sampler'; } -} // Should never fail unless kMaxBindingsPerBindGroup is exceeded, because the validation for +} + +const kCasesForMaxResourcesPerStageTests = params().combine(poptions('maxedType', kBindingTypes)).combine(poptions('maxedVisibility', kShaderStages)).filter(p => (kBindingTypeInfo[p.maxedType].validStages & p.maxedVisibility) !== 0).expand(function* (p) { + for (const extraTypeSame of [true, false]) { + yield* poptions('extraType', pickExtraBindingTypes(p.maxedType, extraTypeSame)); + } +}).combine(poptions('extraVisibility', kShaderStages)).filter(p => (kBindingTypeInfo[p.extraType].validStages & p.extraVisibility) !== 0); // Should never fail unless kMaxBindingsPerBindGroup is exceeded, because the validation for // resources-of-type-per-stage is in pipeline layout creation. -g.test('max resources per stage/in bind group layout', async t => { - const maxedType = t.params.maxedType; - const extraType = t.params.extraType; +g.test('max_resources_per_stage,in_bind_group_layout').params(kCasesForMaxResourcesPerStageTests).fn(async t => { const { + maxedType, + extraType, maxedVisibility, extraVisibility } = t.params; - const maxedCount = kPerStageBindingLimits[kBindingTypeInfo[maxedType].perStageLimitType]; + const maxedTypeInfo = kBindingTypeInfo[maxedType]; + const maxedCount = maxedTypeInfo.perStageLimitClass.max; + const extraTypeInfo = kBindingTypeInfo[extraType]; const maxResourceBindings = []; for (let i = 0; i < maxedCount; i++) { maxResourceBindings.push({ binding: i, visibility: maxedVisibility, - type: maxedType + type: maxedType, + storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined }); } @@ -176,31 +160,35 @@ newDescriptor.entries.push({ binding: maxedCount, visibility: extraVisibility, - type: extraType + type: extraType, + storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined }); const shouldError = maxedCount >= kMaxBindingsPerBindGroup; t.expectValidationError(() => { t.device.createBindGroupLayout(newDescriptor); }, shouldError); -}).params(kCasesForMaxResourcesPerStageTests); // One pipeline layout can have a maximum number of each type of binding *per stage* (which is +}); // One pipeline layout can have a maximum number of each type of binding *per stage* (which is // different for each type). Test that the max works, then add one more binding of same-or-different // type and same-or-different visibility. -g.test('max resources per stage/in pipeline layout', async t => { - const maxedType = t.params.maxedType; - const extraType = t.params.extraType; +g.test('max_resources_per_stage,in_pipeline_layout').params(kCasesForMaxResourcesPerStageTests).fn(async t => { const { + maxedType, + extraType, maxedVisibility, extraVisibility } = t.params; - const maxedCount = kPerStageBindingLimits[kBindingTypeInfo[maxedType].perStageLimitType]; + const maxedTypeInfo = kBindingTypeInfo[maxedType]; + const maxedCount = maxedTypeInfo.perStageLimitClass.max; + const extraTypeInfo = kBindingTypeInfo[extraType]; const maxResourceBindings = []; for (let i = 0; i < maxedCount; i++) { maxResourceBindings.push({ binding: i, visibility: maxedVisibility, - type: maxedType + type: maxedType, + storageTextureFormat: maxedTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined }); } @@ -215,16 +203,17 @@ entries: [{ binding: 0, visibility: extraVisibility, - type: extraType + type: extraType, + storageTextureFormat: extraTypeInfo.resource === 'storageTex' ? 'rgba8unorm' : undefined }] }); // Some binding types use the same limit, e.g. 'storage-buffer' and 'readonly-storage-buffer'. - const newBindingCountsTowardSamePerStageLimit = (maxedVisibility & extraVisibility) !== 0 && kBindingTypeInfo[maxedType].perStageLimitType === kBindingTypeInfo[extraType].perStageLimitType; + const newBindingCountsTowardSamePerStageLimit = (maxedVisibility & extraVisibility) !== 0 && kBindingTypeInfo[maxedType].perStageLimitClass.class === kBindingTypeInfo[extraType].perStageLimitClass.class; const layoutExceedsPerStageLimit = newBindingCountsTowardSamePerStageLimit; t.expectValidationError(() => { t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout, extraLayout] }); }, layoutExceedsPerStageLimit); -}).params(kCasesForMaxResourcesPerStageTests); +}); //# sourceMappingURL=createBindGroupLayout.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/createPipelineLayout.spec.js b/webgpu/webgpu/api/validation/createPipelineLayout.spec.js index f1582c4..262581e 100644 --- a/webgpu/webgpu/api/validation/createPipelineLayout.spec.js +++ b/webgpu/webgpu/api/validation/createPipelineLayout.spec.js
@@ -5,8 +5,9 @@ export const description = ` createPipelineLayout validation tests. `; -import { pbool, pcombine, poptions } from '../../../common/framework/params.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; +import * as C from '../../../common/constants.js'; +import { pbool, poptions, params } from '../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { kBindingTypeInfo, kBindingTypes, kShaderStageCombinations } from '../../capability_info.js'; import { ValidationTest } from './validation_test.js'; @@ -14,16 +15,18 @@ return JSON.parse(JSON.stringify(descriptor)); } -export const g = new TestGroup(ValidationTest); -g.test('number of dynamic buffers exceeds the maximum value', async t => { +export const g = makeTestGroup(ValidationTest); +g.test('number_of_dynamic_buffers_exceeds_the_maximum_value').params(params().combine(poptions('visibility', [0, 2, 4, 6])).combine(poptions('type', [C.BindingType.UniformBuffer, C.BindingType.StorageBuffer, C.BindingType.ReadonlyStorageBuffer]))).fn(async t => { const { type, visibility } = t.params; - const maxDynamicCount = kBindingTypeInfo[type].maxDynamicCount; + const { + maxDynamic + } = kBindingTypeInfo[type].perPipelineLimitClass; const maxDynamicBufferBindings = []; - for (let binding = 0; binding < maxDynamicCount; binding++) { + for (let binding = 0; binding < maxDynamic; binding++) { maxDynamicBufferBindings.push({ binding, visibility, @@ -57,12 +60,13 @@ t.expectValidationError(() => { t.device.createPipelineLayout(badPipelineLayoutDescriptor); }); -}).params(pcombine(poptions('visibility', [0, 2, 4, 6]), // -poptions('type', ['uniform-buffer', 'storage-buffer', 'readonly-storage-buffer']))); -g.test('visibility and dynamic offsets', t => { - const hasDynamicOffset = t.params.hasDynamicOffset; - const type = t.params.type; - const visibility = t.params.visibility; +}); +g.test('visibility_and_dynamic_offsets').params(params().combine(poptions('type', kBindingTypes)).combine(pbool('hasDynamicOffset')).combine(poptions('visibility', kShaderStageCombinations))).fn(t => { + const { + type, + hasDynamicOffset, + visibility + } = t.params; const info = kBindingTypeInfo[type]; const descriptor = { entries: [{ @@ -72,17 +76,17 @@ hasDynamicOffset }] }; + const supportsDynamicOffset = kBindingTypeInfo[type].perPipelineLimitClass.maxDynamic > 0; let success = true; - if (info.type !== 'buffer' && hasDynamicOffset) success = false; + if (!supportsDynamicOffset && hasDynamicOffset) success = false; if ((visibility & ~info.validStages) !== 0) success = false; t.expectValidationError(() => { t.device.createPipelineLayout({ bindGroupLayouts: [t.device.createBindGroupLayout(descriptor)] }); }, !success); -}).params(pcombine(poptions('type', kBindingTypes), // -pbool('hasDynamicOffset'), poptions('visibility', kShaderStageCombinations))); -g.test('number of bind group layouts exceeds the maximum value', async t => { +}); +g.test('number_of_bind_group_layouts_exceeds_the_maximum_value').fn(async t => { const bindGroupLayoutDescriptor = { entries: [] }; // 4 is the maximum number of bind group layouts. diff --git a/webgpu/webgpu/api/validation/createTexture.spec.js b/webgpu/webgpu/api/validation/createTexture.spec.js index ab4c6f8..2ef8d19 100644 --- a/webgpu/webgpu/api/validation/createTexture.spec.js +++ b/webgpu/webgpu/api/validation/createTexture.spec.js
@@ -5,8 +5,8 @@ export const description = ` createTexture validation tests. `; -import { poptions } from '../../../common/framework/params.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { poptions } from '../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { kTextureFormatInfo, kTextureFormats } from '../../capability_info.js'; import { ValidationTest } from './validation_test.js'; @@ -36,23 +36,9 @@ } -export const g = new TestGroup(F); -g.test('validation of sampleCount', async t => { - const { - sampleCount, - mipLevelCount, - arrayLayerCount, - _success - } = t.params; - const descriptor = t.getDescriptor({ - sampleCount, - mipLevelCount, - arrayLayerCount - }); - t.expectValidationError(() => { - t.device.createTexture(descriptor); - }, !_success); -}).params([{ +export const g = makeTestGroup(F); +g.test('validation_of_sampleCount').params([// TODO: Consider making a list of "valid"+"invalid" texture descriptors in capability_info. +{ sampleCount: 0, _success: false }, // sampleCount of 0 is not allowed @@ -84,29 +70,29 @@ sampleCount: 4, mipLevelCount: 2, _success: false -}, // it is an error to create a multisampled texture with mipLevelCount > 1 +}, // multisampled multi-level not allowed { sampleCount: 4, arrayLayerCount: 2, - _success: true -} // multisampled 2D array texture is supported -]); -g.test('validation of mipLevelCount', async t => { + _success: false +} // multisampled multi-layer is not allowed +]).fn(async t => { const { - width, - height, + sampleCount, mipLevelCount, + arrayLayerCount, _success } = t.params; const descriptor = t.getDescriptor({ - width, - height, - mipLevelCount + sampleCount, + mipLevelCount, + arrayLayerCount }); t.expectValidationError(() => { t.device.createTexture(descriptor); }, !_success); -}).params([{ +}); +g.test('validation_of_mipLevelCount').params([{ width: 32, height: 32, mipLevelCount: 1, @@ -160,19 +146,46 @@ mipLevelCount: 6, _success: true } // non square mip map halves the resolution until a 1x1 dimension. (Mip maps: 32 * 8, 16 * 4, 8 * 2, 4 * 1, 2 * 1, 1 * 1) -]); -g.test('it is valid to destroy a texture', t => { +]).fn(async t => { + const { + width, + height, + mipLevelCount, + _success + } = t.params; + const descriptor = t.getDescriptor({ + width, + height, + mipLevelCount + }); + t.expectValidationError(() => { + t.device.createTexture(descriptor); + }, !_success); +}); +g.test('it_is_valid_to_destroy_a_texture').fn(t => { const descriptor = t.getDescriptor(); const texture = t.device.createTexture(descriptor); texture.destroy(); }); -g.test('it is valid to destroy a destroyed texture', t => { +g.test('it_is_valid_to_destroy_a_destroyed_texture').fn(t => { const descriptor = t.getDescriptor(); const texture = t.device.createTexture(descriptor); texture.destroy(); texture.destroy(); }); -g.test('it is invalid to submit a destroyed texture before and after encode', async t => { +g.test('it_is_invalid_to_submit_a_destroyed_texture_before_and_after_encode').params([{ + destroyBeforeEncode: false, + destroyAfterEncode: false, + _success: true +}, { + destroyBeforeEncode: true, + destroyAfterEncode: false, + _success: false +}, { + destroyBeforeEncode: false, + destroyAfterEncode: true, + _success: false +}]).fn(async t => { const { destroyBeforeEncode, destroyAfterEncode, @@ -208,20 +221,8 @@ t.expectValidationError(() => { t.queue.submit([commandBuffer]); }, !_success); -}).params([{ - destroyBeforeEncode: false, - destroyAfterEncode: false, - _success: true -}, { - destroyBeforeEncode: true, - destroyAfterEncode: false, - _success: false -}, { - destroyBeforeEncode: false, - destroyAfterEncode: true, - _success: false -}]); -g.test('it is invalid to have an output attachment texture with non renderable format', async t => { +}); +g.test('it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format').params(poptions('format', kTextureFormats)).fn(async t => { const format = t.params.format; const info = kTextureFormatInfo[format]; const descriptor = t.getDescriptor({ @@ -232,5 +233,5 @@ t.expectValidationError(() => { t.device.createTexture(descriptor); }, !info.renderable); -}).params(poptions('format', kTextureFormats)); // TODO: Add tests for compressed texture formats +}); // TODO: Add tests for compressed texture formats //# sourceMappingURL=createTexture.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/createView.spec.js b/webgpu/webgpu/api/validation/createView.spec.js index d23af58..524d921 100644 --- a/webgpu/webgpu/api/validation/createView.spec.js +++ b/webgpu/webgpu/api/validation/createView.spec.js
@@ -5,7 +5,8 @@ export const description = ` createView validation tests. `; -import { TestGroup } from '../../../common/framework/test_group.js'; +import * as C from '../../../common/constants.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; const ARRAY_LAYER_COUNT_2D = 6; const MIP_LEVEL_COUNT = 6; @@ -55,28 +56,8 @@ } -export const g = new TestGroup(F); -g.test('creating texture view on a 2D non array texture', async t => { - const { - dimension = '2d', - arrayLayerCount, - mipLevelCount, - baseMipLevel, - _success - } = t.params; - const texture = t.createTexture({ - arrayLayerCount: 1 - }); - const descriptor = t.getDescriptor({ - dimension, - arrayLayerCount, - mipLevelCount, - baseMipLevel - }); - t.expectValidationError(() => { - texture.createView(descriptor); - }, !_success); -}).params([{ +export const g = makeTestGroup(F); +g.test('creating_texture_view_on_a_2D_non_array_texture').params([{ _success: true }, // default view works { @@ -88,7 +69,7 @@ _success: false }, // it is an error to view a layer past the end of the texture { - dimension: '2d-array', + dimension: C.TextureViewDimension.E2dArray, arrayLayerCount: 1, _success: true }, // it is OK to create a 1-layer 2D array texture view on a 2D texture @@ -135,30 +116,32 @@ mipLevelCount: 1, baseMipLevel: MIP_LEVEL_COUNT, _success: false -}]); -g.test('creating texture view on a 2D array texture', async t => { +}]).fn(async t => { const { - dimension = '2d-array', + dimension = '2d', arrayLayerCount, - baseArrayLayer, + mipLevelCount, + baseMipLevel, _success } = t.params; const texture = t.createTexture({ - arrayLayerCount: ARRAY_LAYER_COUNT_2D + arrayLayerCount: 1 }); const descriptor = t.getDescriptor({ dimension, arrayLayerCount, - baseArrayLayer + mipLevelCount, + baseMipLevel }); t.expectValidationError(() => { texture.createView(descriptor); }, !_success); -}).params([{ +}); +g.test('creating_texture_view_on_a_2D_array_texture').params([{ _success: true }, // default view works { - dimension: '2d', + dimension: C.TextureViewDimension.E2d, arrayLayerCount: 1, _success: true }, // it is OK to create a 2D texture view on a 2D array texture @@ -200,8 +183,53 @@ arrayLayerCount: 1, baseArrayLayer: ARRAY_LAYER_COUNT_2D, _success: false -}]); -g.test('Using defaults validates the same as setting values for more than 1 array layer', async t => { +}]).fn(async t => { + const { + dimension = '2d-array', + arrayLayerCount, + baseArrayLayer, + _success + } = t.params; + const texture = t.createTexture({ + arrayLayerCount: ARRAY_LAYER_COUNT_2D + }); + const descriptor = t.getDescriptor({ + dimension, + arrayLayerCount, + baseArrayLayer + }); + t.expectValidationError(() => { + texture.createView(descriptor); + }, !_success); +}); +g.test('Using_defaults_validates_the_same_as_setting_values_for_more_than_1_array_layer').params([{ + _success: true +}, { + format: C.TextureFormat.RGBA8Unorm, + _success: true +}, { + format: C.TextureFormat.R8Unorm, + _success: false +}, { + dimension: C.TextureViewDimension.E2dArray, + _success: true +}, { + dimension: C.TextureViewDimension.E2d, + _success: false +}, { + arrayLayerCount: ARRAY_LAYER_COUNT_2D, + _success: false +}, // setting array layers to non-0 means the dimensionality will default to 2D so by itself it causes an error. +{ + arrayLayerCount: ARRAY_LAYER_COUNT_2D, + dimension: C.TextureViewDimension.E2dArray, + _success: true +}, { + arrayLayerCount: ARRAY_LAYER_COUNT_2D, + dimension: C.TextureViewDimension.E2dArray, + mipLevelCount: MIP_LEVEL_COUNT, + _success: true +}]).fn(async t => { const { format, dimension, @@ -221,35 +249,37 @@ t.expectValidationError(() => { texture.createView(descriptor); }, !_success); -}).params([{ +}); +g.test('Using_defaults_validates_the_same_as_setting_values_for_only_1_array_layer').params([{ _success: true }, { - format: 'rgba8unorm', + format: C.TextureFormat.RGBA8Unorm, _success: true }, { - format: 'r8unorm', + format: C.TextureFormat.R8Unorm, _success: false }, { - dimension: '2d-array', + dimension: C.TextureViewDimension.E2dArray, _success: true }, { - dimension: '2d', - _success: false -}, { - arrayLayerCount: ARRAY_LAYER_COUNT_2D, - _success: false -}, // setting array layers to non-0 means the dimensionality will default to 2D so by itself it causes an error. -{ - arrayLayerCount: ARRAY_LAYER_COUNT_2D, - dimension: '2d-array', + dimension: C.TextureViewDimension.E2d, _success: true }, { - arrayLayerCount: ARRAY_LAYER_COUNT_2D, - dimension: '2d-array', + arrayLayerCount: 0, + _success: true +}, { + arrayLayerCount: 1, + _success: true +}, { + arrayLayerCount: 2, + _success: false +}, { mipLevelCount: MIP_LEVEL_COUNT, _success: true -}]); -g.test('Using defaults validates the same as setting values for only 1 array layer', async t => { +}, { + mipLevelCount: 1, + _success: true +}]).fn(async t => { const { format, dimension, @@ -269,37 +299,43 @@ t.expectValidationError(() => { texture.createView(descriptor); }, !_success); -}).params([{ +}); +g.test('creating_cube_map_texture_view').params([{ + dimension: C.TextureViewDimension.Cube, + arrayLayerCount: 6, _success: true -}, { - format: 'rgba8unorm', - _success: true -}, { - format: 'r8unorm', +}, // it is OK to create a cube map texture view with arrayLayerCount == 6 +// it is an error to create a cube map texture view with arrayLayerCount != 6 +{ + dimension: C.TextureViewDimension.Cube, + arrayLayerCount: 3, _success: false }, { - dimension: '2d-array', - _success: true -}, { - dimension: '2d', - _success: true -}, { - arrayLayerCount: 0, - _success: true -}, { - arrayLayerCount: 1, - _success: true -}, { - arrayLayerCount: 2, + dimension: C.TextureViewDimension.Cube, + arrayLayerCount: 7, _success: false }, { - mipLevelCount: MIP_LEVEL_COUNT, - _success: true + dimension: C.TextureViewDimension.Cube, + arrayLayerCount: 12, + _success: false }, { - mipLevelCount: 1, + dimension: C.TextureViewDimension.Cube, + _success: false +}, { + dimension: C.TextureViewDimension.CubeArray, + arrayLayerCount: 12, _success: true -}]); -g.test('creating cube map texture view', async t => { +}, // it is OK to create a cube map array texture view with arrayLayerCount % 6 == 0 +// it is an error to create a cube map array texture view with arrayLayerCount % 6 != 0 +{ + dimension: C.TextureViewDimension.CubeArray, + arrayLayerCount: 11, + _success: false +}, { + dimension: C.TextureViewDimension.CubeArray, + arrayLayerCount: 13, + _success: false +}]).fn(async t => { const { dimension = '2d-array', arrayLayerCount, @@ -315,43 +351,16 @@ t.expectValidationError(() => { texture.createView(descriptor); }, !_success); -}).params([{ - dimension: 'cube', - arrayLayerCount: 6, - _success: true -}, // it is OK to create a cube map texture view with arrayLayerCount == 6 -// it is an error to create a cube map texture view with arrayLayerCount != 6 +}); +g.test('creating_cube_map_texture_view_with_a_non_square_texture').params([{ + dimension: C.TextureViewDimension.Cube, + arrayLayerCount: 6 +}, // it is an error to create a cube map texture view with width != height. { - dimension: 'cube', - arrayLayerCount: 3, - _success: false -}, { - dimension: 'cube', - arrayLayerCount: 7, - _success: false -}, { - dimension: 'cube', - arrayLayerCount: 12, - _success: false -}, { - dimension: 'cube', - _success: false -}, { - dimension: 'cube-array', - arrayLayerCount: 12, - _success: true -}, // it is OK to create a cube map array texture view with arrayLayerCount % 6 == 0 -// it is an error to create a cube map array texture view with arrayLayerCount % 6 != 0 -{ - dimension: 'cube-array', - arrayLayerCount: 11, - _success: false -}, { - dimension: 'cube-array', - arrayLayerCount: 13, - _success: false -}]); -g.test('creating cube map texture view with a non square texture', async t => { + dimension: C.TextureViewDimension.CubeArray, + arrayLayerCount: 12 +} // it is an error to create a cube map array texture view with width != height. +]).fn(async t => { const { dimension, arrayLayerCount @@ -369,17 +378,9 @@ t.expectValidationError(() => { nonSquareTexture.createView(descriptor); }); -}).params([{ - dimension: 'cube', - arrayLayerCount: 6 -}, // it is an error to create a cube map texture view with width != height. -{ - dimension: 'cube-array', - arrayLayerCount: 12 -} // it is an error to create a cube map array texture view with width != height. -]); // TODO: add more tests when rules are fully implemented. +}); // TODO: add more tests when rules are fully implemented. -g.test('test the format compatibility rules when creating a texture view', async t => { +g.test('test_the_format_compatibility_rules_when_creating_a_texture_view').fn(async t => { const texture = t.createTexture({ arrayLayerCount: 1 }); @@ -391,7 +392,7 @@ texture.createView(descriptor); }); }); -g.test('it is invalid to use a texture view created from a destroyed texture', async t => { +g.test('it_is_invalid_to_use_a_texture_view_created_from_a_destroyed_texture').fn(async t => { const texture = t.createTexture({ arrayLayerCount: 1 }); diff --git a/webgpu/webgpu/api/validation/error_scope.spec.js b/webgpu/webgpu/api/validation/error_scope.spec.js index fd69c4e..3edbec2 100644 --- a/webgpu/webgpu/api/validation/error_scope.spec.js +++ b/webgpu/webgpu/api/validation/error_scope.spec.js
@@ -9,7 +9,7 @@ `; import { Fixture } from '../../../common/framework/fixture.js'; import { getGPU } from '../../../common/framework/gpu/implementation.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { assert, raceWithRejectOnTimeout } from '../../../common/framework/util/util.js'; class F extends Fixture { @@ -64,14 +64,14 @@ } -export const g = new TestGroup(F); -g.test('simple case where the error scope catches an error', async t => { +export const g = makeTestGroup(F); +g.test('simple_case_where_the_error_scope_catches_an_error').fn(async t => { t.device.pushErrorScope('validation'); t.createErrorBuffer(); const error = await t.device.popErrorScope(); t.expect(error instanceof GPUValidationError); }); -g.test('errors bubble to the parent scope if not handled by the current scope', async t => { +g.test('errors_bubble_to_the_parent_scope_if_not_handled_by_the_current_scope').fn(async t => { t.device.pushErrorScope('validation'); t.device.pushErrorScope('out-of-memory'); t.createErrorBuffer(); @@ -84,7 +84,7 @@ t.expect(error instanceof GPUValidationError); } }); -g.test('if an error scope matches an error it does not bubble to the parent scope', async t => { +g.test('if_an_error_scope_matches_an_error_it_does_not_bubble_to_the_parent_scope').fn(async t => { t.device.pushErrorScope('validation'); t.device.pushErrorScope('validation'); t.createErrorBuffer(); @@ -97,7 +97,7 @@ t.expect(error === null); } }); -g.test('if no error scope handles an error it fires an uncapturederror event', async t => { +g.test('if_no_error_scope_handles_an_error_it_fires_an_uncapturederror_event').fn(async t => { t.device.pushErrorScope('out-of-memory'); const uncapturedErrorEvent = await t.expectUncapturedError(() => { t.createErrorBuffer(); @@ -106,7 +106,7 @@ const error = await t.device.popErrorScope(); t.expect(error === null); }); -g.test('push/popping sibling error scopes must be balanced', async t => { +g.test('push,popping_sibling_error_scopes_must_be_balanced').fn(async t => { { const promise = t.device.popErrorScope(); t.shouldReject('OperationError', promise); @@ -125,7 +125,7 @@ t.shouldReject('OperationError', promise); } }); -g.test('push/popping nested error scopes must be balanced', async t => { +g.test('push,popping_nested_error_scopes_must_be_balanced').fn(async t => { { const promise = t.device.popErrorScope(); t.shouldReject('OperationError', promise); diff --git a/webgpu/webgpu/api/validation/fences.spec.js b/webgpu/webgpu/api/validation/fences.spec.js index 7747b5e..2ab0691 100644 --- a/webgpu/webgpu/api/validation/fences.spec.js +++ b/webgpu/webgpu/api/validation/fences.spec.js
@@ -5,11 +5,11 @@ export const description = ` fences validation tests. `; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; -export const g = new TestGroup(ValidationTest); // TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided +export const g = makeTestGroup(ValidationTest); // TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided -g.test('wait on a fence without signaling the value is invalid', async t => { +g.test('wait_on_a_fence_without_signaling_the_value_is_invalid').fn(async t => { const fence = t.queue.createFence(); t.expectValidationError(() => { const promise = fence.onCompletion(2); @@ -17,7 +17,7 @@ }); }); // TODO: Remove if https://github.com/gpuweb/gpuweb/issues/377 is decided -g.test('wait on a fence with a value greater than signaled value is invalid', async t => { +g.test('wait_on_a_fence_with_a_value_greater_than_signaled_value_is_invalid').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 2); t.expectValidationError(() => { @@ -25,7 +25,7 @@ t.shouldReject('OperationError', promise); }); }); -g.test('signal a value lower than signaled value is invalid', async t => { +g.test('signal_a_value_lower_than_signaled_value_is_invalid').fn(async t => { const fence = t.queue.createFence({ initialValue: 1 }); @@ -33,7 +33,7 @@ t.queue.signal(fence, 0); }); }); -g.test('signal a value equal to signaled value is invalid', async t => { +g.test('signal_a_value_equal_to_signaled_value_is_invalid').fn(async t => { const fence = t.queue.createFence({ initialValue: 1 }); @@ -41,14 +41,14 @@ t.queue.signal(fence, 1); }); }); -g.test('increasing fence value by more than 1 succeeds', async t => { +g.test('increasing_fence_value_by_more_than_1_succeeds').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 2); await fence.onCompletion(2); t.queue.signal(fence, 6); await fence.onCompletion(6); }); -g.test('signal a fence on a different device than it was created on is invalid', async t => { +g.test('signal_a_fence_on_a_different_device_than_it_was_created_on_is_invalid').fn(async t => { const fence = t.queue.createFence(); const anotherDevice = await t.device.adapter.requestDevice(); const anotherQueue = anotherDevice.defaultQueue; @@ -56,7 +56,7 @@ anotherQueue.signal(fence, 2); }); }); -g.test('signal a fence on a different device does not update fence signaled value', async t => { +g.test('signal_a_fence_on_a_different_device_does_not_update_fence_signaled_value').fn(async t => { const fence = t.queue.createFence({ initialValue: 1 }); diff --git a/webgpu/webgpu/api/validation/queue_submit.spec.js b/webgpu/webgpu/api/validation/queue_submit.spec.js index dbe0bb4..24e386b 100644 --- a/webgpu/webgpu/api/validation/queue_submit.spec.js +++ b/webgpu/webgpu/api/validation/queue_submit.spec.js
@@ -5,10 +5,10 @@ export const description = ` queue submit validation tests. `; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; -export const g = new TestGroup(ValidationTest); -g.test('submitting with a mapped buffer is disallowed', async t => { +export const g = makeTestGroup(ValidationTest); +g.test('submitting_with_a_mapped_buffer_is_disallowed').fn(async t => { const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC diff --git a/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js b/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js index 7c887a1..3c1b111 100644 --- a/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js +++ b/webgpu/webgpu/api/validation/render_pass_descriptor.spec.js
@@ -5,7 +5,7 @@ export const description = ` render pass descriptor validation tests. `; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; class F extends ValidationTest { @@ -67,8 +67,8 @@ } -export const g = new TestGroup(F); -g.test('a render pass with only one color is ok', t => { +export const g = makeTestGroup(F); +g.test('a_render_pass_with_only_one_color_is_ok').fn(t => { const colorTexture = t.createTexture({ format: 'rgba8unorm' }); @@ -77,7 +77,7 @@ }; t.tryRenderPass(true, descriptor); }); -g.test('a render pass with only one depth attachment is ok', t => { +g.test('a_render_pass_with_only_one_depth_attachment_is_ok').fn(t => { const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' }); @@ -87,7 +87,15 @@ }; t.tryRenderPass(true, descriptor); }); -g.test('OOB color attachment indices are handled', async t => { +g.test('OOB_color_attachment_indices_are_handled').params([{ + colorAttachmentsCount: 4, + _success: true +}, // Control case +{ + colorAttachmentsCount: 5, + _success: false +} // Out of bounds +]).fn(async t => { const { colorAttachmentsCount, _success @@ -102,16 +110,8 @@ await t.tryRenderPass(_success, { colorAttachments }); -}).params([{ - colorAttachmentsCount: 4, - _success: true -}, // Control case -{ - colorAttachmentsCount: 5, - _success: false -} // Out of bounds -]); -g.test('attachments must have the same size', async t => { +}); +g.test('attachments_must_have_the_same_size').fn(async t => { const colorTexture1x1A = t.createTexture({ width: 1, height: 1, @@ -161,7 +161,7 @@ await t.tryRenderPass(false, descriptor); } }); -g.test('attachments must match whether they are used for color or depth stencil', async t => { +g.test('attachments_must_match_whether_they_are_used_for_color_or_depth_stencil').fn(async t => { const colorTexture = t.createTexture({ format: 'rgba8unorm' }); @@ -184,7 +184,22 @@ await t.tryRenderPass(false, descriptor); } }); -g.test('check layer count for color or depth stencil', async t => { +g.test('check_layer_count_for_color_or_depth_stencil').params([{ + arrayLayerCount: 5, + baseArrayLayer: 0, + _success: false +}, // using 2D array texture view with arrayLayerCount > 1 is not allowed +{ + arrayLayerCount: 1, + baseArrayLayer: 0, + _success: true +}, // using 2D array texture view that covers the first layer of the texture is OK +{ + arrayLayerCount: 1, + baseArrayLayer: 9, + _success: true +} // using 2D array texture view that covers the last layer is OK for depth stencil +]).fn(async t => { const { arrayLayerCount, baseArrayLayer, @@ -236,23 +251,23 @@ }; await t.tryRenderPass(_success, descriptor); } -}).params([{ - arrayLayerCount: 5, - baseArrayLayer: 0, +}); +g.test('check_mip_level_count_for_color_or_depth_stencil').params([{ + mipLevelCount: 2, + baseMipLevel: 0, _success: false -}, // using 2D array texture view with arrayLayerCount > 1 is not allowed +}, // using 2D texture view with mipLevelCount > 1 is not allowed { - arrayLayerCount: 1, - baseArrayLayer: 0, + mipLevelCount: 1, + baseMipLevel: 0, _success: true -}, // using 2D array texture view that covers the first layer of the texture is OK +}, // using 2D texture view that covers the first level of the texture is OK { - arrayLayerCount: 1, - baseArrayLayer: 9, + mipLevelCount: 1, + baseMipLevel: 3, _success: true -} // using 2D array texture view that covers the last layer is OK for depth stencil -]); -g.test('check mip level count for color or depth stencil', async t => { +} // using 2D texture view that covers the last level of the texture is OK +]).fn(async t => { const { mipLevelCount, baseMipLevel, @@ -304,23 +319,8 @@ }; await t.tryRenderPass(_success, descriptor); } -}).params([{ - mipLevelCount: 2, - baseMipLevel: 0, - _success: false -}, // using 2D texture view with mipLevelCount > 1 is not allowed -{ - mipLevelCount: 1, - baseMipLevel: 0, - _success: true -}, // using 2D texture view that covers the first level of the texture is OK -{ - mipLevelCount: 1, - baseMipLevel: 3, - _success: true -} // using 2D texture view that covers the last level of the texture is OK -]); -g.test('it is invalid to set resolve target if color attachment is non multisampled', async t => { +}); +g.test('it_is_invalid_to_set_resolve_target_if_color_attachment_is_non_multisampled').fn(async t => { const colorTexture = t.createTexture({ sampleCount: 1 }); @@ -341,7 +341,7 @@ }; await t.tryRenderPass(false, descriptor); }); -g.test('check the use of multisampled textures as color attachments', async t => { +g.test('check_the_use_of_multisampled_textures_as_color_attachments').fn(async t => { const colorTexture = t.createTexture({ sampleCount: 1 }); @@ -363,7 +363,7 @@ await t.tryRenderPass(false, descriptor); } }); -g.test('it is invalid to use a multisampled resolve target', async t => { +g.test('it_is_invalid_to_use_a_multisampled_resolve_target').fn(async t => { const multisampledColorTexture = t.createTexture({ sampleCount: 4 }); @@ -377,7 +377,7 @@ }; await t.tryRenderPass(false, descriptor); }); -g.test('it is invalid to use a resolve target with array layer count greater than 1', async t => { +g.test('it_is_invalid_to_use_a_resolve_target_with_array_layer_count_greater_than_1').fn(async t => { const multisampledColorTexture = t.createTexture({ sampleCount: 4 }); @@ -391,7 +391,7 @@ }; await t.tryRenderPass(false, descriptor); }); -g.test('it is invalid to use a resolve target with mipmap level count greater than 1', async t => { +g.test('it_is_invalid_to_use_a_resolve_target_with_mipmap_level_count_greater_than_1').fn(async t => { const multisampledColorTexture = t.createTexture({ sampleCount: 4 }); @@ -405,7 +405,7 @@ }; await t.tryRenderPass(false, descriptor); }); -g.test('it is invalid to use a resolve target whose usage is not output attachment', async t => { +g.test('it_is_invalid_to_use_a_resolve_target_whose_usage_is_not_output_attachment').fn(async t => { const multisampledColorTexture = t.createTexture({ sampleCount: 4 }); @@ -419,7 +419,7 @@ }; await t.tryRenderPass(false, descriptor); }); -g.test('it is invalid to use a resolve target in error state', async t => { +g.test('it_is_invalid_to_use_a_resolve_target_in_error_state').fn(async t => { const ARRAY_LAYER_COUNT = 1; const multisampledColorTexture = t.createTexture({ sampleCount: 4 @@ -440,7 +440,7 @@ }; await t.tryRenderPass(false, descriptor); }); -g.test('use of multisampled attachment and non multisampled resolve target is allowed', async t => { +g.test('use_of_multisampled_attachment_and_non_multisampled_resolve_target_is_allowed').fn(async t => { const multisampledColorTexture = t.createTexture({ sampleCount: 4 }); @@ -454,7 +454,7 @@ }; t.tryRenderPass(true, descriptor); }); -g.test('use a resolve target in a format different than the attachment is not allowed', async t => { +g.test('use_a_resolve_target_in_a_format_different_than_the_attachment_is_not_allowed').fn(async t => { const multisampledColorTexture = t.createTexture({ sampleCount: 4 }); @@ -468,7 +468,7 @@ }; await t.tryRenderPass(false, descriptor); }); -g.test('size of the resolve target must be the same as the color attachment', async t => { +g.test('size_of_the_resolve_target_must_be_the_same_as_the_color_attachment').fn(async t => { const size = 16; const multisampledColorTexture = t.createTexture({ width: size, @@ -504,7 +504,7 @@ t.tryRenderPass(true, descriptor); } }); -g.test('check depth stencil attachment sample counts mismatch', async t => { +g.test('check_depth_stencil_attachment_sample_counts_mismatch').fn(async t => { const multisampledDepthStencilTexture = t.createTexture({ sampleCount: 4, format: 'depth24plus-stencil8' diff --git a/webgpu/webgpu/api/validation/setBindGroup.spec.js b/webgpu/webgpu/api/validation/setBindGroup.spec.js index baf5907..95786a8 100644 --- a/webgpu/webgpu/api/validation/setBindGroup.spec.js +++ b/webgpu/webgpu/api/validation/setBindGroup.spec.js
@@ -5,8 +5,8 @@ export const description = ` setBindGroup validation tests. `; -import { pcombine, poptions } from '../../../common/framework/params.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { poptions, params } from '../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; class F extends ValidationTest { @@ -58,8 +58,8 @@ } -export const g = new TestGroup(F); -g.test('dynamic offsets passed but not expected/compute pass', async t => { +export const g = makeTestGroup(F); +g.test('dynamic_offsets_passed_but_not_expected,compute_pass').params(poptions('type', ['compute', 'renderpass', 'renderbundle'])).fn(async t => { const bindGroupLayout = t.device.createBindGroupLayout({ entries: [] }); @@ -104,8 +104,46 @@ t.fail(); } }); -}).params(poptions('type', ['compute', 'renderpass', 'renderbundle'])); -g.test('dynamic offsets match expectations in pass encoder', async t => { +}); +g.test('dynamic_offsets_match_expectations_in_pass_encoder').params(params().combine(poptions('type', ['compute', 'renderpass', 'renderbundle'])).combine([{ + dynamicOffsets: [256, 0], + _success: true +}, // Dynamic offsets aligned +{ + dynamicOffsets: [1, 2], + _success: false +}, // Dynamic offsets not aligned +// Wrong number of dynamic offsets +{ + dynamicOffsets: [256, 0, 0], + _success: false +}, { + dynamicOffsets: [256], + _success: false +}, { + dynamicOffsets: [], + _success: false +}, // Dynamic uniform buffer out of bounds because of binding size +{ + dynamicOffsets: [512, 0], + _success: false +}, { + dynamicOffsets: [1024, 0], + _success: false +}, { + dynamicOffsets: [0xffffffff, 0], + _success: false +}, // Dynamic storage buffer out of bounds because of binding size +{ + dynamicOffsets: [0, 512], + _success: false +}, { + dynamicOffsets: [0, 1024], + _success: false +}, { + dynamicOffsets: [0, 0xffffffff], + _success: false +}])).fn(async t => { // Dynamic buffer offsets require offset to be divisible by 256 const MIN_DYNAMIC_BUFFER_OFFSET_ALIGNMENT = 256; const BINDING_SIZE = 9; @@ -164,43 +202,5 @@ t.testComputePass(bindGroup, dynamicOffsets); }, !_success); -}).params(pcombine(poptions('type', ['compute', 'renderpass', 'renderbundle']), [{ - dynamicOffsets: [256, 0], - _success: true -}, // Dynamic offsets aligned -{ - dynamicOffsets: [1, 2], - _success: false -}, // Dynamic offsets not aligned -// Wrong number of dynamic offsets -{ - dynamicOffsets: [256, 0, 0], - _success: false -}, { - dynamicOffsets: [256], - _success: false -}, { - dynamicOffsets: [], - _success: false -}, // Dynamic uniform buffer out of bounds because of binding size -{ - dynamicOffsets: [512, 0], - _success: false -}, { - dynamicOffsets: [1024, 0], - _success: false -}, { - dynamicOffsets: [Number.MAX_SAFE_INTEGER, 0], - _success: false -}, // Dynamic storage buffer out of bounds because of binding size -{ - dynamicOffsets: [0, 512], - _success: false -}, { - dynamicOffsets: [0, 1024], - _success: false -}, { - dynamicOffsets: [0, Number.MAX_SAFE_INTEGER], - _success: false -}])); // TODO: test error bind group +}); // TODO: test error bind group //# sourceMappingURL=setBindGroup.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/setBlendColor.spec.js b/webgpu/webgpu/api/validation/setBlendColor.spec.js index b890545..469641d 100644 --- a/webgpu/webgpu/api/validation/setBlendColor.spec.js +++ b/webgpu/webgpu/api/validation/setBlendColor.spec.js
@@ -5,7 +5,7 @@ export const description = ` setBlendColor validation tests. `; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; // TODO: Move beginRenderPass to a Fixture class. class F extends ValidationTest { @@ -34,8 +34,8 @@ } -export const g = new TestGroup(F); -g.test('basic use of setBlendColor', t => { +export const g = makeTestGroup(F); +g.test('basic_use_of_setBlendColor').fn(t => { const commandEncoder = t.device.createCommandEncoder(); const renderPass = t.beginRenderPass(commandEncoder); renderPass.setBlendColor({ @@ -47,7 +47,7 @@ renderPass.endPass(); commandEncoder.finish(); }); -g.test('setBlendColor allows any number value', t => { +g.test('setBlendColor_allows_any_number_value').fn(t => { const values = [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]; for (const value of values) { diff --git a/webgpu/webgpu/api/validation/setScissorRect.spec.js b/webgpu/webgpu/api/validation/setScissorRect.spec.js index e12c90d..8381865 100644 --- a/webgpu/webgpu/api/validation/setScissorRect.spec.js +++ b/webgpu/webgpu/api/validation/setScissorRect.spec.js
@@ -5,7 +5,7 @@ export const description = ` setScissorRect validation tests. `; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; const TEXTURE_WIDTH = 16; const TEXTURE_HEIGHT = 16; // TODO: Move this fixture class to a common file. @@ -36,23 +36,8 @@ } -export const g = new TestGroup(F); -g.test('use of setScissorRect', async t => { - const { - x, - y, - width, - height, - _success - } = t.params; - const commandEncoder = t.device.createCommandEncoder(); - const renderPass = t.beginRenderPass(commandEncoder); - renderPass.setScissorRect(x, y, width, height); - renderPass.endPass(); - t.expectValidationError(() => { - commandEncoder.finish(); - }, !_success); -}).params([{ +export const g = makeTestGroup(F); +g.test('use_of_setScissorRect').params([{ x: 0, y: 0, width: 1, @@ -87,5 +72,20 @@ height: TEXTURE_HEIGHT + 1, _success: true } // Scissor larger than the framebuffer is allowed -]); +]).fn(async t => { + const { + x, + y, + width, + height, + _success + } = t.params; + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = t.beginRenderPass(commandEncoder); + renderPass.setScissorRect(x, y, width, height); + renderPass.endPass(); + t.expectValidationError(() => { + commandEncoder.finish(); + }, !_success); +}); //# sourceMappingURL=setScissorRect.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/setStencilReference.spec.js b/webgpu/webgpu/api/validation/setStencilReference.spec.js index cbd48ae..f205239 100644 --- a/webgpu/webgpu/api/validation/setStencilReference.spec.js +++ b/webgpu/webgpu/api/validation/setStencilReference.spec.js
@@ -5,8 +5,8 @@ export const description = ` setStencilReference validation tests. `; -import { poptions } from '../../../common/framework/params.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { poptions } from '../../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; // TODO: Move this fixture class to a common file. class F extends ValidationTest { @@ -35,8 +35,8 @@ } -export const g = new TestGroup(F); -g.test('use of setStencilReference', t => { +export const g = makeTestGroup(F); +g.test('use_of_setStencilReference').params(poptions('reference', [0, 0xffffffff])).fn(t => { const { reference } = t.params; @@ -45,5 +45,5 @@ renderPass.setStencilReference(reference); renderPass.endPass(); commandEncoder.finish(); -}).params(poptions('reference', [0, 0xffffffff])); +}); //# sourceMappingURL=setStencilReference.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/setViewport.spec.js b/webgpu/webgpu/api/validation/setViewport.spec.js index 9a3009f..f1168c1 100644 --- a/webgpu/webgpu/api/validation/setViewport.spec.js +++ b/webgpu/webgpu/api/validation/setViewport.spec.js
@@ -5,7 +5,7 @@ export const description = ` setViewport validation tests. `; -import { TestGroup } from '../../../common/framework/test_group.js'; +import { makeTestGroup } from '../../../common/framework/test_group.js'; import { ValidationTest } from './validation_test.js'; const TEXTURE_WIDTH = 16; const TEXTURE_HEIGHT = 16; // TODO: Move this fixture class to a common file. @@ -36,25 +36,8 @@ } -export const g = new TestGroup(F); -g.test('use of setViewport', async t => { - const { - x, - y, - width, - height, - minDepth, - maxDepth, - _success - } = t.params; - const commandEncoder = t.device.createCommandEncoder(); - const renderPass = t.beginRenderPass(commandEncoder); - renderPass.setViewport(x, y, width, height, minDepth, maxDepth); - renderPass.endPass(); - t.expectValidationError(() => { - commandEncoder.finish(); - }, !_success); -}).params([{ +export const g = makeTestGroup(F); +g.test('use_of_setViewport').params([{ x: 0, y: 0, width: 1, @@ -189,5 +172,22 @@ maxDepth: 1, _success: true } // Viewport larger than the framebuffer is allowed -]); +]).fn(async t => { + const { + x, + y, + width, + height, + minDepth, + maxDepth, + _success + } = t.params; + const commandEncoder = t.device.createCommandEncoder(); + const renderPass = t.beginRenderPass(commandEncoder); + renderPass.setViewport(x, y, width, height, minDepth, maxDepth); + renderPass.endPass(); + t.expectValidationError(() => { + commandEncoder.finish(); + }, !_success); +}); //# sourceMappingURL=setViewport.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/api/validation/validation_test.js b/webgpu/webgpu/api/validation/validation_test.js index 92d8a06..17eb97b 100644 --- a/webgpu/webgpu/api/validation/validation_test.js +++ b/webgpu/webgpu/api/validation/validation_test.js
@@ -4,42 +4,6 @@ import { unreachable } from '../../../common/framework/util/util.js'; import { GPUTest } from '../../gpu_test.js'; -export let BindingResourceType; - -(function (BindingResourceType) { - BindingResourceType["error-buffer"] = "error-buffer"; - BindingResourceType["error-sampler"] = "error-sampler"; - BindingResourceType["error-textureview"] = "error-textureview"; - BindingResourceType["uniform-buffer"] = "uniform-buffer"; - BindingResourceType["storage-buffer"] = "storage-buffer"; - BindingResourceType["sampler"] = "sampler"; - BindingResourceType["sampled-textureview"] = "sampled-textureview"; - BindingResourceType["storage-textureview"] = "storage-textureview"; -})(BindingResourceType || (BindingResourceType = {})); - -export function resourceBindingMatches(b, r) { - switch (b) { - case 'storage-buffer': - case 'readonly-storage-buffer': - return r === 'storage-buffer'; - - case 'sampled-texture': - return r === 'sampled-textureview'; - - case 'sampler': - return r === 'sampler'; - - case 'readonly-storage-texture': - case 'writeonly-storage-texture': - return r === 'storage-textureview'; - - case 'uniform-buffer': - return r === 'uniform-buffer'; - - default: - unreachable('unknown GPUBindingType'); - } -} export class ValidationTest extends GPUTest { getStorageBuffer() { return this.device.createBuffer({ @@ -70,6 +34,12 @@ return this.device.createSampler(); } + getComparisonSampler() { + return this.device.createSampler({ + compare: 'never' + }); + } + getErrorSampler() { this.device.pushErrorScope('validation'); const sampler = this.device.createSampler({ @@ -120,34 +90,37 @@ getBindingResource(bindingType) { switch (bindingType) { - case 'error-buffer': + case 'errorBuf': return { buffer: this.getErrorBuffer() }; - case 'error-sampler': + case 'errorSamp': return this.getErrorSampler(); - case 'error-textureview': + case 'errorTex': return this.getErrorTextureView(); - case 'uniform-buffer': + case 'uniformBuf': return { buffer: this.getUniformBuffer() }; - case 'storage-buffer': + case 'storageBuf': return { buffer: this.getStorageBuffer() }; - case 'sampler': + case 'plainSamp': return this.getSampler(); - case 'sampled-textureview': + case 'compareSamp': + return this.getComparisonSampler(); + + case 'sampledTex': return this.getSampledTexture().createView(); - case 'storage-textureview': + case 'storageTex': return this.getStorageTexture().createView(); default: @@ -170,7 +143,7 @@ if (!gpuValidationError) { niceStack.message = 'Validation error was expected.'; - this.rec.fail(niceStack); + this.rec.validationFailed(niceStack); } else if (gpuValidationError instanceof GPUValidationError) { niceStack.message = `Captured validation error - ${gpuValidationError.message}`; this.rec.debug(niceStack); diff --git a/webgpu/webgpu/capability_info.js b/webgpu/webgpu/capability_info.js index 0e336b9..b2d9353 100644 --- a/webgpu/webgpu/capability_info.js +++ b/webgpu/webgpu/capability_info.js
@@ -2,7 +2,16 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -import * as C from '../common/constants.js'; // Textures +import * as C from '../common/constants.js'; + +function keysOf(obj) { + return Object.keys(obj); +} + +function numericKeysOf(obj) { + return Object.keys(obj).map(n => Number(n)); +} // Textures + export const kTextureFormatInfo = /* prettier-ignore */ @@ -12,230 +21,611 @@ // 8-bit formats 'r8unorm': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 1, + blockWidth: 1, + blockHeight: 1 }, 'r8snorm': { renderable: false, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 1, + blockWidth: 1, + blockHeight: 1 }, 'r8uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 1, + blockWidth: 1, + blockHeight: 1 }, 'r8sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 1, + blockWidth: 1, + blockHeight: 1 }, // 16-bit formats 'r16uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 2, + blockWidth: 1, + blockHeight: 1 }, 'r16sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 2, + blockWidth: 1, + blockHeight: 1 }, 'r16float': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 2, + blockWidth: 1, + blockHeight: 1 }, 'rg8unorm': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 2, + blockWidth: 1, + blockHeight: 1 }, 'rg8snorm': { renderable: false, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 2, + blockWidth: 1, + blockHeight: 1 }, 'rg8uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 2, + blockWidth: 1, + blockHeight: 1 }, 'rg8sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 2, + blockWidth: 1, + blockHeight: 1 }, // 32-bit formats 'r32uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'r32sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'r32float': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rg16uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rg16sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rg16float': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rgba8unorm': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rgba8unorm-srgb': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rgba8snorm': { renderable: false, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rgba8uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rgba8sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'bgra8unorm': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'bgra8unorm-srgb': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, // Packed 32-bit formats 'rgb10a2unorm': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'rg11b10float': { renderable: false, - color: true + color: true, + depth: false, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, // 64-bit formats 'rg32uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 8, + blockWidth: 1, + blockHeight: 1 }, 'rg32sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 8, + blockWidth: 1, + blockHeight: 1 }, 'rg32float': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 8, + blockWidth: 1, + blockHeight: 1 }, 'rgba16uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 8, + blockWidth: 1, + blockHeight: 1 }, 'rgba16sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 8, + blockWidth: 1, + blockHeight: 1 }, 'rgba16float': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 8, + blockWidth: 1, + blockHeight: 1 }, // 128-bit formats 'rgba32uint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 16, + blockWidth: 1, + blockHeight: 1 }, 'rgba32sint': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 16, + blockWidth: 1, + blockHeight: 1 }, 'rgba32float': { renderable: true, - color: true + color: true, + depth: false, + stencil: false, + storage: true, + copyable: true, + bytesPerBlock: 16, + blockWidth: 1, + blockHeight: 1 }, // Depth/stencil formats 'depth32float': { renderable: true, - color: false + color: false, + depth: true, + stencil: false, + storage: false, + copyable: true, + bytesPerBlock: 4, + blockWidth: 1, + blockHeight: 1 }, 'depth24plus': { renderable: true, - color: false + color: false, + depth: true, + stencil: false, + storage: false, + copyable: false }, 'depth24plus-stencil8': { renderable: true, - color: false + color: false, + depth: true, + stencil: true, + storage: false, + copyable: false } }; -export const kTextureFormats = Object.keys(kTextureFormatInfo); // Bindings +export const kTextureFormats = keysOf(kTextureFormatInfo); +export const kTextureDimensionInfo = +/* prettier-ignore */ +{ + '1d': {}, + '2d': {}, + '3d': {} +}; +export const kTextureDimensions = keysOf(kTextureDimensionInfo); +export const kTextureAspectInfo = +/* prettier-ignore */ +{ + 'all': {}, + 'depth-only': {}, + 'stencil-only': {} +}; +export const kTextureAspects = keysOf(kTextureAspectInfo); +export const kTextureUsageInfo = { + [C.TextureUsage.CopySrc]: {}, + [C.TextureUsage.CopyDst]: {}, + [C.TextureUsage.Sampled]: {}, + [C.TextureUsage.Storage]: {}, + [C.TextureUsage.OutputAttachment]: {} +}; +export const kTextureUsages = numericKeysOf(kTextureUsageInfo); // Typedefs for bindings +// Bindings export const kMaxBindingsPerBindGroup = 16; export const kPerStageBindingLimits = /* prettier-ignore */ { - 'uniform-buffer': 12, - 'storage-buffer': 4, - 'sampler': 16, - 'sampled-texture': 16, - 'storage-texture': 4 + 'uniformBuf': { + class: 'uniformBuf', + max: 12 + }, + 'storageBuf': { + class: 'storageBuf', + max: 4 + }, + 'sampler': { + class: 'sampler', + max: 16 + }, + 'sampledTex': { + class: 'sampledTex', + max: 16 + }, + 'storageTex': { + class: 'storageTex', + max: 4 + } }; -const kStagesAll = C.ShaderStage.Vertex | C.ShaderStage.Fragment | C.ShaderStage.Compute; -const kStagesCompute = C.ShaderStage.Compute; -export const kBindingTypeInfo = +export const kPerPipelineBindingLimits = +/* prettier-ignore */ +{ + 'uniformBuf': { + class: 'uniformBuf', + maxDynamic: 8 + }, + 'storageBuf': { + class: 'storageBuf', + maxDynamic: 4 + }, + 'sampler': { + class: 'sampler', + maxDynamic: 0 + }, + 'sampledTex': { + class: 'sampledTex', + maxDynamic: 0 + }, + 'storageTex': { + class: 'storageTex', + maxDynamic: 0 + } +}; +const kBindableResource = +/* prettier-ignore */ +{ + uniformBuf: {}, + storageBuf: {}, + plainSamp: {}, + compareSamp: {}, + sampledTex: {}, + storageTex: {}, + errorBuf: {}, + errorSamp: {}, + errorTex: {} +}; +export const kBindableResources = keysOf(kBindableResource); +const kBindingKind = +/* prettier-ignore */ +{ + uniformBuf: { + resource: 'uniformBuf', + perStageLimitClass: kPerStageBindingLimits.uniformBuf, + perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf + }, + storageBuf: { + resource: 'storageBuf', + perStageLimitClass: kPerStageBindingLimits.storageBuf, + perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf + }, + plainSamp: { + resource: 'plainSamp', + perStageLimitClass: kPerStageBindingLimits.sampler, + perPipelineLimitClass: kPerPipelineBindingLimits.sampler + }, + compareSamp: { + resource: 'compareSamp', + perStageLimitClass: kPerStageBindingLimits.sampler, + perPipelineLimitClass: kPerPipelineBindingLimits.sampler + }, + sampledTex: { + resource: 'sampledTex', + perStageLimitClass: kPerStageBindingLimits.sampledTex, + perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex + }, + storageTex: { + resource: 'storageTex', + perStageLimitClass: kPerStageBindingLimits.storageTex, + perPipelineLimitClass: kPerPipelineBindingLimits.storageTex + } +}; // Binding type info + +const kValidStagesAll = { + validStages: C.ShaderStage.Vertex | C.ShaderStage.Fragment | C.ShaderStage.Compute +}; +const kValidStagesStorageWrite = { + validStages: C.ShaderStage.Fragment | C.ShaderStage.Compute +}; +export const kBufferBindingTypeInfo = /* prettier-ignore */ { 'uniform-buffer': { - type: 'buffer', - validStages: kStagesAll, - perStageLimitType: 'uniform-buffer', - maxDynamicCount: 8 + usage: C.BufferUsage.Uniform, + ...kBindingKind.uniformBuf, + ...kValidStagesAll }, 'storage-buffer': { - type: 'buffer', - validStages: kStagesCompute, - perStageLimitType: 'storage-buffer', - maxDynamicCount: 4 + usage: C.BufferUsage.Storage, + ...kBindingKind.storageBuf, + ...kValidStagesStorageWrite }, 'readonly-storage-buffer': { - type: 'buffer', - validStages: kStagesAll, - perStageLimitType: 'storage-buffer', - maxDynamicCount: 4 - }, - 'sampler': { - type: 'sampler', - validStages: kStagesAll, - perStageLimitType: 'sampler', - maxDynamicCount: 0 - }, - 'comparison-sampler': { - type: 'sampler', - validStages: kStagesAll, - perStageLimitType: 'sampler', - maxDynamicCount: 0 - }, - 'sampled-texture': { - type: 'texture', - validStages: kStagesAll, - perStageLimitType: 'sampled-texture', - maxDynamicCount: 0 - }, - 'writeonly-storage-texture': { - type: 'texture', - validStages: kStagesCompute, - perStageLimitType: 'storage-texture', - maxDynamicCount: 0 - }, - 'readonly-storage-texture': { - type: 'texture', - validStages: kStagesAll, - perStageLimitType: 'storage-texture', - maxDynamicCount: 0 + usage: C.BufferUsage.Storage, + ...kBindingKind.storageBuf, + ...kValidStagesAll } }; -export const kBindingTypes = Object.keys(kBindingTypeInfo); +export const kBufferBindingTypes = keysOf(kBufferBindingTypeInfo); +export const kSamplerBindingTypeInfo = +/* prettier-ignore */ +{ + 'sampler': { ...kBindingKind.plainSamp, + ...kValidStagesAll + }, + 'comparison-sampler': { ...kBindingKind.compareSamp, + ...kValidStagesAll + } +}; +export const kSamplerBindingTypes = keysOf(kSamplerBindingTypeInfo); +export const kTextureBindingTypeInfo = +/* prettier-ignore */ +{ + 'sampled-texture': { + usage: C.TextureUsage.Sampled, + ...kBindingKind.sampledTex, + ...kValidStagesAll + }, + 'writeonly-storage-texture': { + usage: C.TextureUsage.Storage, + ...kBindingKind.storageTex, + ...kValidStagesStorageWrite + }, + 'readonly-storage-texture': { + usage: C.TextureUsage.Storage, + ...kBindingKind.storageTex, + ...kValidStagesAll + } +}; +export const kTextureBindingTypes = keysOf(kTextureBindingTypeInfo); // All binding types (merged from above) + +export const kBindingTypeInfo = { ...kBufferBindingTypeInfo, + ...kSamplerBindingTypeInfo, + ...kTextureBindingTypeInfo +}; +export const kBindingTypes = keysOf(kBindingTypeInfo); export const kShaderStages = [C.ShaderStage.Vertex, C.ShaderStage.Fragment, C.ShaderStage.Compute]; export const kShaderStageCombinations = [0, 1, 2, 3, 4, 5, 6, 7]; //# sourceMappingURL=capability_info.js.map \ No newline at end of file diff --git a/webgpu/webgpu/examples.spec.js b/webgpu/webgpu/examples.spec.js index add23f7..28b9ae4 100644 --- a/webgpu/webgpu/examples.spec.js +++ b/webgpu/webgpu/examples.spec.js
@@ -7,7 +7,7 @@ Start here when looking for examples of basic framework usage. `; -import { TestGroup } from '../common/framework/test_group.js'; +import { makeTestGroup } from '../common/framework/test_group.js'; import { GPUTest } from './gpu_test.js'; // To run these tests in the standalone runner, run `grunt build` or `grunt pre` then open: // - http://localhost:8080/?runnow=1&q=webgpu:examples: // To run in WPT, copy/symlink the out-wpt/ directory as the webgpu/ directory in WPT, then open: @@ -18,10 +18,10 @@ // - ?q=webgpu:examples:basic/ // - ?q=webgpu:examples: -export const g = new TestGroup(GPUTest); // Note: spaces in test names are replaced with underscores: webgpu:examples:test_name= +export const g = makeTestGroup(GPUTest); // Note: spaces in test names are replaced with underscores: webgpu:examples:test_name= -g.test('test name', t => {}); -g.test('basic', t => { +g.test('test_name').fn(t => {}); +g.test('basic').fn(t => { t.expect(true); t.expect(true, 'true should be true'); t.shouldThrow( // The expected '.name' of the thrown error. @@ -31,7 +31,7 @@ }, // Log message. 'function should throw Error'); }); -g.test('basic/async', async t => { +g.test('basic,async').fn(async t => { // shouldReject must be awaited to ensure it can wait for the promise before the test ends. t.shouldReject( // The expected '.name' of the thrown error. 'TypeError', // Promise expected to reject. @@ -50,9 +50,7 @@ // - webgpu:examples:basic/params={"x":2,"y":4} runs with t.params = {x: 2, y: 5, _result: 6}. // - webgpu:examples:basic/params={"x":-10,"y":18} runs with t.params = {x: -10, y: 18, _result: 8}. -g.test('basic/params', t => { - t.expect(t.params.x + t.params.y === t.params._result); -}).params([{ +g.test('basic,params').params([{ x: 2, y: 4, _result: 6 @@ -61,15 +59,17 @@ x: -10, y: 18, _result: 8 -}]); // (note the blank comment above to enforce newlines on autoformat) +}]).fn(t => { + t.expect(t.params.x + t.params.y === t.params._result); +}); // (note the blank comment above to enforce newlines on autoformat) -g.test('gpu/async', async t => { +g.test('gpu,async').fn(async t => { const fence = t.queue.createFence(); t.queue.signal(fence, 2); await fence.onCompletion(1); t.expect(fence.getCompletedValue() === 2); }); -g.test('gpu/buffers', async t => { +g.test('gpu,buffers').fn(async t => { const data = new Uint32Array([0, 1234, 0]); const [src, map] = t.device.createBufferMapped({ size: 12, diff --git a/webgpu/webgpu/gpu_test.js b/webgpu/webgpu/gpu_test.js index 0202f34..d2528c8 100644 --- a/webgpu/webgpu/gpu_test.js +++ b/webgpu/webgpu/gpu_test.js
@@ -5,49 +5,11 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import { Fixture } from '../common/framework/fixture.js'; -import { getGPU } from '../common/framework/gpu/implementation.js'; -import { assert, unreachable } from '../common/framework/util/util.js'; - -class DevicePool { - constructor() { - _defineProperty(this, "device", undefined); - - _defineProperty(this, "state", 'uninitialized'); - } - - async initialize() { - try { - const gpu = getGPU(); - const adapter = await gpu.requestAdapter(); - this.device = await adapter.requestDevice(); - } catch (ex) { - this.state = 'failed'; - throw ex; - } - } - - async acquire() { - assert(this.state !== 'acquired', 'Device was in use'); - assert(this.state !== 'failed', 'Failed to initialize WebGPU device'); - const state = this.state; - this.state = 'acquired'; - - if (state === 'uninitialized') { - await this.initialize(); - } - - assert(!!this.device); - return this.device; - } - - release(device) { - assert(this.state === 'acquired'); - assert(device === this.device, 'Released device was the wrong device'); - this.state = 'free'; - } - -} - +import { DevicePool, TestOOMedShouldAttemptGC } from '../common/framework/gpu/device_pool.js'; +import { attemptGarbageCollection } from '../common/framework/util/collect_garbage.js'; +import { assert } from '../common/framework/util/util.js'; +import { fillTextureDataWithTexelValue, getTextureCopyLayout } from './util/texture/layout.js'; +import { getTexelDataRepresentation } from './util/texture/texelData.js'; const devicePool = new DevicePool(); export class GPUTest extends Fixture { constructor(...args) { @@ -76,39 +38,33 @@ device, queue }; + } // Note: finalize is called even if init was unsuccessful. - try { - await device.popErrorScope(); - unreachable('There was an error scope on the stack at the beginning of the test'); - } catch (ex) {} - - device.pushErrorScope('out-of-memory'); - device.pushErrorScope('validation'); - this.initialized = true; - } async finalize() { - // Note: finalize is called even if init was unsuccessful. await super.finalize(); - if (this.initialized) { - const gpuValidationError = await this.device.popErrorScope(); - - if (gpuValidationError !== null) { - assert(gpuValidationError instanceof GPUValidationError); - this.fail(`Unexpected validation error occurred: ${gpuValidationError.message}`); - } - - const gpuOutOfMemoryError = await this.device.popErrorScope(); - - if (gpuOutOfMemoryError !== null) { - assert(gpuOutOfMemoryError instanceof GPUOutOfMemoryError); - this.fail('Unexpected out-of-memory error occurred'); - } - } - if (this.objects) { - devicePool.release(this.objects.device); + let threw; + { + const objects = this.objects; + this.objects = undefined; + + try { + await devicePool.release(objects.device); + } catch (ex) { + threw = ex; + } + } // The GPUDevice and GPUQueue should now have no outstanding references. + + if (threw) { + if (threw instanceof TestOOMedShouldAttemptGC) { + // Try to clean up, in case there are stray GPU resources in need of collection. + await attemptGarbageCollection(); + } + + throw threw; + } } } @@ -125,15 +81,15 @@ expectContents(src, expected) { - const exp = new Uint8Array(expected.buffer, expected.byteOffset, expected.byteLength); const dst = this.createCopyForMapRead(src, expected.buffer.byteLength); this.eventualAsyncExpectation(async niceStack => { - const actual = new Uint8Array((await dst.mapReadAsync())); - const check = this.checkBuffer(actual, exp); + const constructor = expected.constructor; + const actual = new constructor((await dst.mapReadAsync())); + const check = this.checkBuffer(actual, expected); if (check !== undefined) { niceStack.message = check; - this.rec.fail(niceStack); + this.rec.expectationFailed(niceStack); } dst.destroy(); @@ -144,31 +100,43 @@ const check = this.checkBuffer(actual, exp); if (check !== undefined) { - this.rec.fail(new Error(check)); + this.rec.expectationFailed(new Error(check)); } } - checkBuffer(actual, exp) { + checkBuffer(actual, exp, tolerance = 0) { + assert(actual.constructor === exp.constructor); const size = exp.byteLength; if (actual.byteLength !== size) { return 'size mismatch'; } - const lines = []; - let failedPixels = 0; + const failedByteIndices = []; + const failedByteExpectedValues = []; + const failedByteActualValues = []; for (let i = 0; i < size; ++i) { - if (actual[i] !== exp[i]) { - if (failedPixels > 4) { - lines.push('... and more'); + const tol = typeof tolerance === 'function' ? tolerance(i) : tolerance; + + if (Math.abs(actual[i] - exp[i]) > tol) { + if (failedByteIndices.length >= 4) { + failedByteIndices.push('...'); + failedByteExpectedValues.push('...'); + failedByteActualValues.push('...'); break; } - failedPixels++; - lines.push(`at [${i}], expected ${exp[i]}, got ${actual[i]}`); + failedByteIndices.push(i.toString()); + failedByteExpectedValues.push(exp[i].toString()); + failedByteActualValues.push(actual[i].toString()); } - } // TODO: Could make a more convenient message, which could look like e.g.: + } + + const summary = `at [${failedByteIndices.join(', ')}], \ +expected [${failedByteExpectedValues.join(', ')}], \ +got [${failedByteActualValues.join(', ')}]`; + const lines = [summary]; // TODO: Could make a more convenient message, which could look like e.g.: // // Starting at offset 48, // got 22222222 ABCDABCD 99999999 @@ -183,20 +151,55 @@ // Or, maybe these diffs aren't actually very useful (given we have the prints just above here), // and we should remove them. More important will be logging of texture data in a visual format. - - if (size <= 256 && failedPixels > 0) { - const expHex = Array.from(exp).map(x => x.toString(16).padStart(2, '0')).join(''); - const actHex = Array.from(actual).map(x => x.toString(16).padStart(2, '0')).join(''); - lines.push('EXPECT: ' + expHex); - lines.push('ACTUAL: ' + actHex); + if (size <= 256 && failedByteIndices.length > 0) { + const expHex = Array.from(new Uint8Array(exp.buffer, exp.byteOffset, exp.byteLength)).map(x => x.toString(16).padStart(2, '0')).join(''); + const actHex = Array.from(new Uint8Array(actual.buffer, actual.byteOffset, actual.byteLength)).map(x => x.toString(16).padStart(2, '0')).join(''); + lines.push('EXPECT:\t ' + exp.join(' ')); + lines.push('\t0x' + expHex); + lines.push('ACTUAL:\t ' + actual.join(' ')); + lines.push('\t0x' + actHex); } - if (failedPixels) { + if (failedByteIndices.length) { return lines.join('\n'); } return undefined; } + expectSingleColor(src, format, { + size, + exp, + dimension = '2d', + slice = 0, + layout + }) { + const { + byteLength, + bytesPerRow, + rowsPerImage, + mipSize + } = getTextureCopyLayout(format, dimension, size, layout); + const expectedTexelData = getTexelDataRepresentation(format).getBytes(exp); + const buffer = this.device.createBuffer({ + size: byteLength, + usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST + }); + const commandEncoder = this.device.createCommandEncoder(); + commandEncoder.copyTextureToBuffer({ + texture: src, + mipLevel: layout?.mipLevel, + arrayLayer: slice + }, { + buffer, + bytesPerRow, + rowsPerImage + }, mipSize); + this.queue.submit([commandEncoder.finish()]); + const arrayBuffer = new ArrayBuffer(byteLength); + fillTextureDataWithTexelValue(expectedTexelData, format, dimension, arrayBuffer, size, layout); + this.expectContents(buffer, new Uint8Array(arrayBuffer)); + } + } //# sourceMappingURL=gpu_test.js.map \ No newline at end of file diff --git a/webgpu/webgpu/listing.js b/webgpu/webgpu/listing.js index 5b9ef1a..ea23ab2 100644 --- a/webgpu/webgpu/listing.js +++ b/webgpu/webgpu/listing.js
@@ -2,151 +2,295 @@ export const listing = [ { - "path": "", - "description": "WebGPU conformance test suite." + "file": [], + "readme": "WebGPU conformance test suite." }, { - "path": "api/", - "description": "Tests for full coverage of the Javascript API surface of WebGPU." + "file": [ + "api" + ], + "readme": "Tests for full coverage of the Javascript API surface of WebGPU." }, { - "path": "api/operation/", - "description": "Tests that check the result of performing valid WebGPU operations, taking advantage of\nparameterization to exercise interactions between features." + "file": [ + "api", + "operation" + ], + "readme": "Tests that check the result of performing valid WebGPU operations, taking advantage of\nparameterization to exercise interactions between features." }, { - "path": "api/operation/buffers/", - "description": "GPUBuffer tests." + "file": [ + "api", + "operation", + "buffers" + ], + "readme": "GPUBuffer tests." }, { - "path": "api/operation/buffers/create_mapped", + "file": [ + "api", + "operation", + "buffers", + "create_mapped" + ], "description": "" }, { - "path": "api/operation/buffers/map", + "file": [ + "api", + "operation", + "buffers", + "map" + ], "description": "" }, { - "path": "api/operation/buffers/map_detach", + "file": [ + "api", + "operation", + "buffers", + "map_detach" + ], "description": "" }, { - "path": "api/operation/buffers/map_oom", + "file": [ + "api", + "operation", + "buffers", + "map_oom" + ], "description": "" }, { - "path": "api/operation/command_buffer/basic", + "file": [ + "api", + "operation", + "command_buffer", + "basic" + ], "description": "Basic tests." }, { - "path": "api/operation/command_buffer/copies", + "file": [ + "api", + "operation", + "command_buffer", + "copies" + ], "description": "copy{Buffer,Texture}To{Buffer,Texture} tests." }, { - "path": "api/operation/command_buffer/render/basic", + "file": [ + "api", + "operation", + "command_buffer", + "render", + "basic" + ], "description": "Basic command buffer rendering tests." }, { - "path": "api/operation/fences", + "file": [ + "api", + "operation", + "fences" + ], "description": "" }, { - "path": "api/regression/", - "description": "One-off tests that reproduce API bugs found in implementations to prevent the bugs from\nappearing again." + "file": [ + "api", + "operation", + "resource_init", + "copied_texture_clear" + ], + "description": "Test uninitialized textures are initialized to zero when copied." }, { - "path": "api/validation/", - "description": "Positive and negative tests for all the validation rules of the API." + "file": [ + "api", + "regression" + ], + "readme": "One-off tests that reproduce API bugs found in implementations to prevent the bugs from\nappearing again." }, { - "path": "api/validation/createBindGroup", + "file": [ + "api", + "validation" + ], + "readme": "Positive and negative tests for all the validation rules of the API." + }, + { + "file": [ + "api", + "validation", + "createBindGroup" + ], "description": "createBindGroup validation tests." }, { - "path": "api/validation/createBindGroupLayout", + "file": [ + "api", + "validation", + "createBindGroupLayout" + ], "description": "createBindGroupLayout validation tests." }, { - "path": "api/validation/createPipelineLayout", + "file": [ + "api", + "validation", + "createPipelineLayout" + ], "description": "createPipelineLayout validation tests." }, { - "path": "api/validation/createTexture", + "file": [ + "api", + "validation", + "createTexture" + ], "description": "createTexture validation tests." }, { - "path": "api/validation/createView", + "file": [ + "api", + "validation", + "createView" + ], "description": "createView validation tests." }, { - "path": "api/validation/error_scope", + "file": [ + "api", + "validation", + "error_scope" + ], "description": "error scope validation tests." }, { - "path": "api/validation/fences", + "file": [ + "api", + "validation", + "fences" + ], "description": "fences validation tests." }, { - "path": "api/validation/queue_submit", + "file": [ + "api", + "validation", + "queue_submit" + ], "description": "queue submit validation tests." }, { - "path": "api/validation/render_pass_descriptor", + "file": [ + "api", + "validation", + "render_pass_descriptor" + ], "description": "render pass descriptor validation tests." }, { - "path": "api/validation/setBindGroup", + "file": [ + "api", + "validation", + "setBindGroup" + ], "description": "setBindGroup validation tests." }, { - "path": "api/validation/setBlendColor", + "file": [ + "api", + "validation", + "setBlendColor" + ], "description": "setBlendColor validation tests." }, { - "path": "api/validation/setScissorRect", + "file": [ + "api", + "validation", + "setScissorRect" + ], "description": "setScissorRect validation tests." }, { - "path": "api/validation/setStencilReference", + "file": [ + "api", + "validation", + "setStencilReference" + ], "description": "setStencilReference validation tests." }, { - "path": "api/validation/setViewport", + "file": [ + "api", + "validation", + "setViewport" + ], "description": "setViewport validation tests." }, { - "path": "examples", + "file": [ + "examples" + ], "description": "Examples of writing CTS tests with various features.\n\nStart here when looking for examples of basic framework usage." }, { - "path": "idl/", - "description": "Tests to check that the WebGPU IDL is correctly implemented, for examples that objects exposed\nexactly the correct members, and that methods throw when passed incomplete dictionaries." + "file": [ + "idl" + ], + "readme": "Tests to check that the WebGPU IDL is correctly implemented, for examples that objects exposed\nexactly the correct members, and that methods throw when passed incomplete dictionaries." }, { - "path": "shader/", - "description": "Tests for full coverage of the shaders that can be passed to WebGPU." + "file": [ + "shader" + ], + "readme": "Tests for full coverage of the shaders that can be passed to WebGPU." }, { - "path": "shader/execution/", - "description": "Tests that check the result of valid shader execution." + "file": [ + "shader", + "execution" + ], + "readme": "Tests that check the result of valid shader execution." }, { - "path": "shader/regression/", - "description": "One-off tests that reproduce shader bugs found in implementations to prevent the bugs from\nappearing again." + "file": [ + "shader", + "regression" + ], + "readme": "One-off tests that reproduce shader bugs found in implementations to prevent the bugs from\nappearing again." }, { - "path": "shader/validation/", - "description": "Positive and negative tests for all the validation rules of the shading language." + "file": [ + "shader", + "validation" + ], + "readme": "Positive and negative tests for all the validation rules of the shading language." }, { - "path": "web-platform/", - "description": "Tests for Web platform-specific interactions like GPUSwapChain and canvas, WebXR,\nImageBitmaps, and video APIs." + "file": [ + "web-platform" + ], + "readme": "Tests for Web platform-specific interactions like GPUSwapChain and canvas, WebXR,\nImageBitmaps, and video APIs." }, { - "path": "web-platform/canvas/context_creation", + "file": [ + "web-platform", + "canvas", + "context_creation" + ], "description": "" }, { - "path": "web-platform/copyImageBitmapToTexture", + "file": [ + "web-platform", + "copyImageBitmapToTexture" + ], "description": "copy imageBitmap To texture tests." } ]; diff --git a/webgpu/webgpu/util/conversion.js b/webgpu/webgpu/util/conversion.js new file mode 100644 index 0000000..73c04fe --- /dev/null +++ b/webgpu/webgpu/util/conversion.js
@@ -0,0 +1,61 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { assert } from '../../common/framework/util/util.js'; +export function floatAsNormalizedInteger(float, bits, signed) { + if (signed) { + assert(float >= -1 && float <= 1); + const max = Math.pow(2, bits - 1) - 1; + return Math.round(float * max); + } else { + assert(float >= 0 && float <= 1); + const max = Math.pow(2, bits) - 1; + return Math.round(float * max); + } +} // Does not handle clamping, underflow, overflow, denormalized numbers + +export function float32ToFloatBits(n, signBits, exponentBits, fractionBits, bias) { + assert(exponentBits <= 8); + assert(fractionBits <= 23); + assert(Number.isFinite(n)); + + if (n === 0) { + return 0; + } + + if (signBits === 0) { + assert(n >= 0); + } + + const buf = new DataView(new ArrayBuffer(Float32Array.BYTES_PER_ELEMENT)); + buf.setFloat32(0, n, true); + const bits = buf.getUint32(0, true); // bits (32): seeeeeeeefffffffffffffffffffffff + + const fractionBitsToDiscard = 23 - fractionBits; // 0 or 1 + + const sign = bits >> 31 & signBits; // >> to remove fraction, & to remove sign, - 127 to remove bias. + + const exp = (bits >> 23 & 0xff) - 127; // Convert to the new biased exponent. + + const newBiasedExp = bias + exp; + assert(newBiasedExp >= 0 && newBiasedExp < 1 << exponentBits); // Mask only the fraction, and discard the lower bits. + + const newFraction = (bits & 0x7fffff) >> fractionBitsToDiscard; + return sign << exponentBits + fractionBits | newBiasedExp << fractionBits | newFraction; +} +export function assertInIntegerRange(n, bits, signed) { + if (signed) { + const min = -Math.pow(2, bits - 1); + const max = Math.pow(2, bits - 1) - 1; + assert(n >= min && n <= max); + } else { + const max = Math.pow(2, bits) - 1; + assert(n >= 0 && n <= max); + } +} +export function gammaCompress(n) { + n = n <= 0.0031308 ? 12.92 * n : 1.055 * Math.pow(n, 1 / 2.4) - 0.055; + return n < 0 ? 0 : n > 1 ? 1 : n; +} +//# sourceMappingURL=conversion.js.map \ No newline at end of file diff --git a/webgpu/webgpu/util/math.js b/webgpu/webgpu/util/math.js new file mode 100644 index 0000000..19720d6 --- /dev/null +++ b/webgpu/webgpu/util/math.js
@@ -0,0 +1,11 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +export function align(n, alignment) { + return Math.ceil(n / alignment) * alignment; +} +export function isAligned(n, alignment) { + return n === align(n, alignment); +} +//# sourceMappingURL=math.js.map \ No newline at end of file diff --git a/webgpu/webgpu/util/texture/layout.js b/webgpu/webgpu/util/texture/layout.js new file mode 100644 index 0000000..db9d5db --- /dev/null +++ b/webgpu/webgpu/util/texture/layout.js
@@ -0,0 +1,126 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import * as C from '../../../common/constants.js'; +import { assert, unreachable } from '../../../common/framework/util/util.js'; +import { kTextureFormatInfo } from '../../capability_info.js'; +import { align, isAligned } from '../math.js'; +export const kBytesPerRowAlignment = 256; +export const kBufferCopyAlignment = 4; +const kDefaultLayoutOptions = { + mipLevel: 0, + bytesPerRow: undefined, + rowsPerImage: undefined +}; +export function getMipSizePassthroughLayers(dimension, size, mipLevel) { + const shiftMinOne = n => Math.max(1, n >> mipLevel); + + switch (dimension) { + case '1d': + assert(size[2] === 1); + return [shiftMinOne(size[0]), size[1], size[2]]; + + case '2d': + return [shiftMinOne(size[0]), shiftMinOne(size[1]), size[2]]; + + case '3d': + return [shiftMinOne(size[0]), shiftMinOne(size[1]), shiftMinOne(size[2])]; + + default: + unreachable(); + } +} +export function getTextureCopyLayout(format, dimension, size, options = kDefaultLayoutOptions) { + const { + mipLevel + } = options; + let { + bytesPerRow, + rowsPerImage + } = options; + const mipSize = getMipSizePassthroughLayers(dimension, size, mipLevel); + const { + blockWidth, + blockHeight, + bytesPerBlock + } = kTextureFormatInfo[format]; + assert(!!bytesPerBlock && !!blockWidth && !!blockHeight); + assert(isAligned(mipSize[0], blockWidth)); + const minBytesPerRow = mipSize[0] / blockWidth * bytesPerBlock; + const alignedMinBytesPerRow = align(minBytesPerRow, kBytesPerRowAlignment); + + if (bytesPerRow !== undefined) { + assert(bytesPerRow >= alignedMinBytesPerRow); + assert(isAligned(bytesPerRow, kBytesPerRowAlignment)); + } else { + bytesPerRow = alignedMinBytesPerRow; + } + + if (rowsPerImage !== undefined) { + assert(rowsPerImage >= mipSize[1]); + } else { + rowsPerImage = mipSize[1]; + } + + assert(isAligned(rowsPerImage, blockHeight)); + const bytesPerSlice = bytesPerRow * (rowsPerImage / blockHeight); + const sliceSize = bytesPerRow * (mipSize[1] / blockHeight - 1) + bytesPerBlock * (mipSize[0] / blockWidth); + const byteLength = bytesPerSlice * (mipSize[2] - 1) + sliceSize; + return { + bytesPerBlock, + byteLength: align(byteLength, kBufferCopyAlignment), + minBytesPerRow, + bytesPerRow, + rowsPerImage, + mipSize + }; +} +export function fillTextureDataWithTexelValue(texelValue, format, dimension, outputBuffer, size, options = kDefaultLayoutOptions) { + const { + blockWidth, + blockHeight, + bytesPerBlock + } = kTextureFormatInfo[format]; + assert(!!bytesPerBlock && !!blockWidth && !!blockHeight); + assert(bytesPerBlock === texelValue.byteLength); + const { + byteLength, + rowsPerImage, + bytesPerRow + } = getTextureCopyLayout(format, dimension, size, options); + assert(byteLength <= outputBuffer.byteLength); + const mipSize = getMipSizePassthroughLayers(dimension, size, options.mipLevel); + const texelValueBytes = new Uint8Array(texelValue); + const outputTexelValueBytes = new Uint8Array(outputBuffer); + + for (let slice = 0; slice < mipSize[2]; ++slice) { + for (let row = 0; row < mipSize[1]; row += blockHeight) { + for (let col = 0; col < mipSize[0]; col += blockWidth) { + const byteOffset = slice * rowsPerImage * bytesPerRow + row * bytesPerRow + col * texelValue.byteLength; + outputTexelValueBytes.set(texelValueBytes, byteOffset); + } + } + } +} +export function createTextureUploadBuffer(texelValue, device, format, dimension, size, options = kDefaultLayoutOptions) { + const { + byteLength, + bytesPerRow, + rowsPerImage, + bytesPerBlock + } = getTextureCopyLayout(format, dimension, size, options); + const [buffer, mapping] = device.createBufferMapped({ + size: byteLength, + usage: C.BufferUsage.CopySrc + }); + assert(texelValue.byteLength === bytesPerBlock); + fillTextureDataWithTexelValue(texelValue, format, dimension, mapping, size, options); + buffer.unmap(); + return { + buffer, + bytesPerRow, + rowsPerImage + }; +} +//# sourceMappingURL=layout.js.map \ No newline at end of file diff --git a/webgpu/webgpu/util/texture/subresource.js b/webgpu/webgpu/util/texture/subresource.js new file mode 100644 index 0000000..04a8e72 --- /dev/null +++ b/webgpu/webgpu/util/texture/subresource.js
@@ -0,0 +1,54 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +function endOfRange(r) { + return 'count' in r ? r.begin + r.count : r.end; +} + +function* rangeAsIterator(r) { + for (let i = r.begin; i < endOfRange(r); ++i) { + yield i; + } +} + +export class SubresourceRange { + constructor(subresources) { + _defineProperty(this, "mipRange", void 0); + + _defineProperty(this, "sliceRange", void 0); + + this.mipRange = { + begin: subresources.mipRange.begin, + end: endOfRange(subresources.mipRange) + }; + this.sliceRange = { + begin: subresources.sliceRange.begin, + end: endOfRange(subresources.sliceRange) + }; + } + + *each() { + for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) { + for (let slice = this.sliceRange.begin; slice < this.sliceRange.end; ++slice) { + yield { + level, + slice + }; + } + } + } + + *mipLevels() { + for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) { + yield { + level, + slices: rangeAsIterator(this.sliceRange) + }; + } + } + +} +//# sourceMappingURL=subresource.js.map \ No newline at end of file diff --git a/webgpu/webgpu/util/texture/texelData.js b/webgpu/webgpu/util/texture/texelData.js new file mode 100644 index 0000000..2ea7275e --- /dev/null +++ b/webgpu/webgpu/util/texture/texelData.js
@@ -0,0 +1,455 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import { assert, unreachable } from '../../../common/framework/util/util.js'; +import { kTextureFormatInfo } from '../../capability_info.js'; +import { assertInIntegerRange, float32ToFloatBits, floatAsNormalizedInteger, gammaCompress } from '../conversion.js'; +export let TexelComponent; + +(function (TexelComponent) { + TexelComponent["R"] = "R"; + TexelComponent["G"] = "G"; + TexelComponent["B"] = "B"; + TexelComponent["A"] = "A"; + TexelComponent["Depth"] = "Depth"; + TexelComponent["Stencil"] = "Stencil"; +})(TexelComponent || (TexelComponent = {})); + +var TexelWriteType; // Function to convert a value into a texel value. It returns the converted value +// and the type of the converted value. For example, conversion may convert: +// - floats to unsigned normalized integers +// - floats to half floats, interpreted as uint16 bits + +(function (TexelWriteType) { + TexelWriteType[TexelWriteType["Sint"] = 0] = "Sint"; + TexelWriteType[TexelWriteType["Uint"] = 1] = "Uint"; + TexelWriteType[TexelWriteType["Float"] = 2] = "Float"; +})(TexelWriteType || (TexelWriteType = {})); + +const kR = [TexelComponent.R]; +const kRG = [TexelComponent.R, TexelComponent.G]; +const kRGB = [TexelComponent.R, TexelComponent.G, TexelComponent.B]; +const kRGBA = [TexelComponent.R, TexelComponent.G, TexelComponent.B, TexelComponent.A]; +const kBGRA = [TexelComponent.B, TexelComponent.G, TexelComponent.R, TexelComponent.A]; + +const unorm = bitLength => n => ({ + value: floatAsNormalizedInteger(n, bitLength, false), + type: TexelWriteType.Uint +}); + +const snorm = bitLength => n => ({ + value: floatAsNormalizedInteger(n, bitLength, true), + type: TexelWriteType.Sint +}); + +const uint = bitLength => n => ({ + value: (assertInIntegerRange(n, bitLength, false), n), + type: TexelWriteType.Uint +}); + +const sint = bitLength => n => ({ + value: (assertInIntegerRange(n, bitLength, true), n), + type: TexelWriteType.Sint +}); + +const unorm2 = { + write: unorm(2), + bitLength: 2 +}; +const unorm8 = { + write: unorm(8), + bitLength: 8 +}; +const unorm10 = { + write: unorm(10), + bitLength: 10 +}; +const snorm8 = { + write: snorm(8), + bitLength: 8 +}; +const uint8 = { + write: uint(8), + bitLength: 8 +}; +const uint16 = { + write: uint(16), + bitLength: 16 +}; +const uint32 = { + write: uint(32), + bitLength: 32 +}; +const sint8 = { + write: sint(8), + bitLength: 8 +}; +const sint16 = { + write: sint(16), + bitLength: 16 +}; +const sint32 = { + write: sint(32), + bitLength: 32 +}; +const float10 = { + write: n => ({ + value: float32ToFloatBits(n, 0, 5, 5, 15), + type: TexelWriteType.Uint + }), + bitLength: 10 +}; +const float11 = { + write: n => ({ + value: float32ToFloatBits(n, 0, 5, 6, 15), + type: TexelWriteType.Uint + }), + bitLength: 11 +}; +const float16 = { + write: n => ({ + value: float32ToFloatBits(n, 1, 5, 10, 15), + type: TexelWriteType.Uint + }), + bitLength: 16 +}; +const float32 = { + write: n => ({ + value: Math.fround(n), + type: TexelWriteType.Float + }), + bitLength: 32 +}; + +const repeatComponents = (componentOrder, perComponentInfo) => { + const componentInfo = componentOrder.reduce((acc, curr) => { + return Object.assign(acc, { + [curr]: perComponentInfo + }); + }, {}); + return { + componentOrder, + componentInfo + }; +}; + +const kRepresentationInfo = +/* prettier-ignore */ +{ + 'r8unorm': { ...repeatComponents(kR, unorm8), + sRGB: false + }, + 'r8snorm': { ...repeatComponents(kR, snorm8), + sRGB: false + }, + 'r8uint': { ...repeatComponents(kR, uint8), + sRGB: false + }, + 'r8sint': { ...repeatComponents(kR, sint8), + sRGB: false + }, + 'r16uint': { ...repeatComponents(kR, uint16), + sRGB: false + }, + 'r16sint': { ...repeatComponents(kR, sint16), + sRGB: false + }, + 'r16float': { ...repeatComponents(kR, float16), + sRGB: false + }, + 'rg8unorm': { ...repeatComponents(kRG, unorm8), + sRGB: false + }, + 'rg8snorm': { ...repeatComponents(kRG, snorm8), + sRGB: false + }, + 'rg8uint': { ...repeatComponents(kRG, uint8), + sRGB: false + }, + 'rg8sint': { ...repeatComponents(kRG, sint8), + sRGB: false + }, + 'r32uint': { ...repeatComponents(kR, uint32), + sRGB: false + }, + 'r32sint': { ...repeatComponents(kR, sint32), + sRGB: false + }, + 'r32float': { ...repeatComponents(kR, float32), + sRGB: false + }, + 'rg16uint': { ...repeatComponents(kRG, uint16), + sRGB: false + }, + 'rg16sint': { ...repeatComponents(kRG, sint16), + sRGB: false + }, + 'rg16float': { ...repeatComponents(kRG, float16), + sRGB: false + }, + 'rgba8unorm': { ...repeatComponents(kRGBA, unorm8), + sRGB: false + }, + 'rgba8unorm-srgb': { ...repeatComponents(kRGBA, unorm8), + sRGB: true + }, + 'rgba8snorm': { ...repeatComponents(kRGBA, snorm8), + sRGB: false + }, + 'rgba8uint': { ...repeatComponents(kRGBA, uint8), + sRGB: false + }, + 'rgba8sint': { ...repeatComponents(kRGBA, sint8), + sRGB: false + }, + 'bgra8unorm': { ...repeatComponents(kBGRA, unorm8), + sRGB: false + }, + 'bgra8unorm-srgb': { ...repeatComponents(kBGRA, unorm8), + sRGB: true + }, + 'rg32uint': { ...repeatComponents(kRG, uint32), + sRGB: false + }, + 'rg32sint': { ...repeatComponents(kRG, sint32), + sRGB: false + }, + 'rg32float': { ...repeatComponents(kRG, float32), + sRGB: false + }, + 'rgba16uint': { ...repeatComponents(kRGBA, uint16), + sRGB: false + }, + 'rgba16sint': { ...repeatComponents(kRGBA, sint16), + sRGB: false + }, + 'rgba16float': { ...repeatComponents(kRGBA, float16), + sRGB: false + }, + 'rgba32uint': { ...repeatComponents(kRGBA, uint32), + sRGB: false + }, + 'rgba32sint': { ...repeatComponents(kRGBA, sint32), + sRGB: false + }, + 'rgba32float': { ...repeatComponents(kRGBA, float32), + sRGB: false + }, + 'rgb10a2unorm': { + componentOrder: kRGBA, + componentInfo: { + R: unorm10, + G: unorm10, + B: unorm10, + A: unorm2 + }, + sRGB: false + }, + 'rg11b10float': { + componentOrder: kRGB, + componentInfo: { + R: float11, + G: float11, + B: float10 + }, + sRGB: false + }, + 'depth32float': { + componentOrder: [TexelComponent.Depth], + componentInfo: { + Depth: float32 + }, + sRGB: false + }, + 'depth24plus': { + componentOrder: [TexelComponent.Depth], + componentInfo: { + Depth: null + }, + sRGB: false + }, + 'depth24plus-stencil8': { + componentOrder: [TexelComponent.Depth, TexelComponent.Stencil], + componentInfo: { + Depth: null, + Stencil: null + }, + sRGB: false + } +}; + +class TexelDataRepresentationImpl { + // TODO: Determine endianness of the GPU data? + constructor(format, componentOrder, componentInfo, sRGB) { + this.format = format; + this.componentOrder = componentOrder; + this.componentInfo = componentInfo; + this.sRGB = sRGB; + + _defineProperty(this, "isGPULittleEndian", true); + } + + totalBitLength() { + return this.componentOrder.reduce((acc, curr) => { + return acc + this.componentInfo[curr].bitLength; + }, 0); + } + + setComponent(data, component, n) { + const componentIndex = this.componentOrder.indexOf(component); + assert(componentIndex !== -1); + const bitOffset = this.componentOrder.slice(0, componentIndex).reduce((acc, curr) => { + const componentInfo = this.componentInfo[curr]; + assert(!!componentInfo); + return acc + componentInfo.bitLength; + }, 0); + const componentInfo = this.componentInfo[component]; + assert(!!componentInfo); + const { + write, + bitLength + } = componentInfo; + const { + value, + type + } = write(n); + + switch (type) { + case TexelWriteType.Float: + { + const byteOffset = Math.floor(bitOffset / 8); + const byteLength = Math.ceil(bitLength / 8); + assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8); + + switch (byteLength) { + case 4: + new DataView(data, byteOffset, byteLength).setFloat32(0, value, this.isGPULittleEndian); + break; + + default: + unreachable(); + } + + break; + } + + case TexelWriteType.Sint: + { + const byteOffset = Math.floor(bitOffset / 8); + const byteLength = Math.ceil(bitLength / 8); + assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8); + + switch (byteLength) { + case 1: + new DataView(data, byteOffset, byteLength).setInt8(0, value); + break; + + case 2: + new DataView(data, byteOffset, byteLength).setInt16(0, value, this.isGPULittleEndian); + break; + + case 4: + new DataView(data, byteOffset, byteLength).setInt32(0, value, this.isGPULittleEndian); + break; + + default: + unreachable(); + } + + break; + } + + case TexelWriteType.Uint: + { + const byteOffset = Math.floor(bitOffset / 8); + const byteLength = Math.ceil(bitLength / 8); + + if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) { + switch (byteLength) { + case 1: + new DataView(data, byteOffset, byteLength).setUint8(0, value); + break; + + case 2: + new DataView(data, byteOffset, byteLength).setUint16(0, value, this.isGPULittleEndian); + break; + + case 4: + new DataView(data, byteOffset, byteLength).setUint32(0, value, this.isGPULittleEndian); + break; + + default: + unreachable(); + } + } else { + // Packed representations are all 32-bit and use Uint as the data type. + // ex.) rg10b11float, rgb10a2unorm + switch (this.totalBitLength()) { + case 32: + { + const view = new DataView(data); + const currentValue = view.getUint32(0, this.isGPULittleEndian); + let mask = 0xffffffff; + const bitsToClearRight = bitOffset; + const bitsToClearLeft = 32 - (bitLength + bitOffset); + mask = mask >>> bitsToClearRight << bitsToClearRight; + mask = mask << bitsToClearLeft >>> bitsToClearLeft; + const newValue = currentValue & ~mask | value << bitOffset; + view.setUint32(0, newValue, this.isGPULittleEndian); + break; + } + + default: + unreachable(); + } + } + + break; + } + + default: + unreachable(); + } + } + + getBytes(components) { + if (this.sRGB) { + components = Object.assign({}, components); + assert(components.R !== undefined); + assert(components.G !== undefined); + assert(components.B !== undefined); + [components.R, components.G, components.B] = [gammaCompress(components.R), gammaCompress(components.G), gammaCompress(components.B)]; + } + + const bytesPerBlock = kTextureFormatInfo[this.format].bytesPerBlock; + assert(!!bytesPerBlock); + const data = new ArrayBuffer(bytesPerBlock); + + for (const c of this.componentOrder) { + const componentValue = components[c]; + assert(componentValue !== undefined); + this.setComponent(data, c, componentValue); + } + + return data; + } + +} + +const kRepresentationCache = new Map(); +export function getTexelDataRepresentation(format) { + if (!kRepresentationCache.has(format)) { + const { + componentOrder, + componentInfo, + sRGB + } = kRepresentationInfo[format]; + kRepresentationCache.set(format, new TexelDataRepresentationImpl(format, componentOrder, componentInfo, sRGB)); + } + + return kRepresentationCache.get(format); +} +//# sourceMappingURL=texelData.js.map \ No newline at end of file diff --git a/webgpu/webgpu/web-platform/canvas/context_creation.spec.js b/webgpu/webgpu/web-platform/canvas/context_creation.spec.js index fd8593b..427b15f 100644 --- a/webgpu/webgpu/web-platform/canvas/context_creation.spec.js +++ b/webgpu/webgpu/web-platform/canvas/context_creation.spec.js
@@ -2,11 +2,11 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/ -export const description = ``; +export const description = ''; import { Fixture } from '../../../common/framework/fixture.js'; -import { TestGroup } from '../../../common/framework/test_group.js'; -export const g = new TestGroup(Fixture); -g.test('canvas element getContext returns GPUCanvasContext', async t => { +import { makeTestGroup } from '../../../common/framework/test_group.js'; +export const g = makeTestGroup(Fixture); +g.test('canvas_element_getContext_returns_GPUCanvasContext').fn(async t => { if (typeof document === 'undefined') { // Skip if there is no document (Workers, Node) t.skip('DOM is not available to create canvas element'); diff --git a/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js b/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js index 5e84113..de97855 100644 --- a/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js +++ b/webgpu/webgpu/web-platform/copyImageBitmapToTexture.spec.js
@@ -5,8 +5,8 @@ export const description = ` copy imageBitmap To texture tests. `; -import { pcombine, poptions } from '../../common/framework/params.js'; -import { TestGroup } from '../../common/framework/test_group.js'; +import { poptions, params } from '../../common/framework/params_builder.js'; +import { makeTestGroup } from '../../common/framework/test_group.js'; import { GPUTest } from '../gpu_test.js'; function calculateRowPitch(width, bytesPerPixel) { @@ -26,7 +26,7 @@ if (check !== undefined) { niceStack.message = check; - this.rec.fail(niceStack); + this.rec.expectationFailed(niceStack); } dst.destroy(); @@ -34,10 +34,11 @@ } checkBufferWithRowPitch(actual, exp, width, height, rowPitch, bytesPerPixel) { - const lines = []; - let failedPixels = 0; + const failedByteIndices = []; + const failedByteExpectedValues = []; + const failedByteActualValues = []; - for (let i = 0; i < height; ++i) { + iLoop: for (let i = 0; i < height; ++i) { const bytesPerRow = width * bytesPerPixel; for (let j = 0; j < bytesPerRow; ++j) { @@ -45,22 +46,27 @@ const indexActual = j + rowPitch * i; if (actual[indexActual] !== exp[indexExp]) { - if (failedPixels > 4) { - break; + if (failedByteIndices.length >= 4) { + failedByteIndices.push('...'); + failedByteExpectedValues.push('...'); + failedByteActualValues.push('...'); + break iLoop; } - failedPixels++; - lines.push(`at [${indexExp}], expected ${exp[indexExp]}, got ${actual[indexActual]}`); + failedByteIndices.push(`(${i},${j})`); + failedByteExpectedValues.push(exp[indexExp].toString()); + failedByteActualValues.push(actual[indexActual].toString()); } } - - if (failedPixels > 4) { - lines.push('... and more'); - break; - } } - return failedPixels > 0 ? lines.join('\n') : undefined; + if (failedByteIndices.length > 0) { + return `at [${failedByteIndices.join(', ')}], \ +expected [${failedByteExpectedValues.join(', ')}], \ +got [${failedByteActualValues.join(', ')}]`; + } + + return undefined; } doTestAndCheckResult(imageBitmapCopyView, dstTextureCopyView, copySize, bytesPerPixel, expectedData) { @@ -95,22 +101,36 @@ } -export const g = new TestGroup(F); -g.test('from ImageData', async t => { +export const g = makeTestGroup(F); +g.test('from_ImageData').params(params().combine(poptions('width', [1, 2, 4, 15, 255, 256])).combine(poptions('height', [1, 2, 4, 15, 255, 256])).combine(poptions('alpha', ['none', 'premultiply'])).combine(poptions('orientation', ['none', 'flipY']))).fn(async t => { const { width, - height + height, + alpha, + orientation } = t.params; // The texture format is rgba8unorm, so the bytes per pixel is 4. const bytesPerPixel = 4; const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height); - for (let i = 0; i < width * height * bytesPerPixel; ++i) { - imagePixels[i] = i % 4 === 3 ? 255 : i % 256; + if (alpha === 'premultiply') { + // Make expected value simple to construct: + // Input is (255, 255, 255, a), which will be stored into the ImageBitmap + // as (a, a, a, a). + for (let i = 0; i < width * height * bytesPerPixel; ++i) { + imagePixels[i] = i % 4 !== 3 ? 255 : i % 256; + } + } else { + for (let i = 0; i < width * height * bytesPerPixel; ++i) { + imagePixels[i] = i % 4 === 3 ? 255 : i % 256; + } } const imageData = new ImageData(imagePixels, width, height); - const imageBitmap = await createImageBitmap(imageData); + const imageBitmap = await createImageBitmap(imageData, { + premultiplyAlpha: alpha, + imageOrientation: orientation + }); const dst = t.device.createTexture({ size: { width: imageBitmap.width, @@ -119,7 +139,35 @@ }, format: 'rgba8unorm', usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC - }); + }); // Construct expected value + + const expectedPixels = new Uint8ClampedArray(bytesPerPixel * width * height); + + for (let i = 0; i < width * height * bytesPerPixel; ++i) { + expectedPixels[i] = imagePixels[i]; + } + + if (orientation === 'flipY') { + for (let i = 0; i < height; ++i) { + for (let j = 0; j < width * bytesPerPixel; ++j) { + const pos_image_pixel = (height - i - 1) * width * bytesPerPixel + j; + const pos_expected_value = i * width * bytesPerPixel + j; + expectedPixels[pos_expected_value] = imagePixels[pos_image_pixel]; + } + } + } + + if (alpha === 'premultiply') { + for (let i = 0; i < width * height * bytesPerPixel; ++i) { + const alpha_value_position = 3 - i % 4 + i; + + if (i % 4 !== 3) { + // Expected value is (a, a, a, a) + expectedPixels[i] = expectedPixels[alpha_value_position]; + } + } + } + t.doTestAndCheckResult({ imageBitmap, origin: { @@ -132,10 +180,9 @@ width: imageBitmap.width, height: imageBitmap.height, depth: 1 - }, bytesPerPixel, imagePixels); -}).params(pcombine(poptions('width', [1, 2, 4, 15, 255, 256]), // -poptions('height', [1, 2, 4, 15, 255, 256]))); -g.test('from canvas', async t => { + }, bytesPerPixel, expectedPixels); +}); +g.test('from_canvas').params(params().combine(poptions('width', [1, 2, 4, 15, 255, 256])).combine(poptions('height', [1, 2, 4, 15, 255, 256]))).fn(async t => { const { width, height @@ -171,7 +218,7 @@ const imagePixels = new Uint8ClampedArray(bytesPerPixel * width * height); for (let i = 0; i < width * height * bytesPerPixel; ++i) { - imagePixels[i] = i % 256; + imagePixels[i] = i % 4 === 3 ? 255 : i % 256; } const imageData = new ImageData(imagePixels, width, height); @@ -185,7 +232,8 @@ }, format: 'rgba8unorm', usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC - }); + }); // This will get origin data and even it has premultiplied-alpha + const expectedData = imageCanvasContext.getImageData(0, 0, imageBitmap.width, imageBitmap.height).data; t.doTestAndCheckResult({ imageBitmap, @@ -200,6 +248,5 @@ height: imageBitmap.height, depth: 1 }, bytesPerPixel, expectedData); -}).params(pcombine(poptions('width', [1, 2, 4, 15, 255, 256]), // -poptions('height', [1, 2, 4, 15, 255, 256]))); +}); //# sourceMappingURL=copyImageBitmapToTexture.spec.js.map \ No newline at end of file diff --git a/webgpu/webgpu/web-platform/reftests/canvas_clear.html b/webgpu/webgpu/web-platform/reftests/canvas_clear.html new file mode 100644 index 0000000..86a3da9 --- /dev/null +++ b/webgpu/webgpu/web-platform/reftests/canvas_clear.html
@@ -0,0 +1,10 @@ +<html class="reftest-wait"> + <title>WebGPU canvas_clear</title> + <meta charset="utf-8" /> + <link rel="help" href="https://gpuweb.github.io/gpuweb/" /> + <meta name="assert" content="WebGPU cleared canvas should be presented correctly" /> + <link rel="match" href="./ref/canvas_clear-ref.html" /> + <canvas id="gpucanvas" width="10" height="10"></canvas> + <script src="/common/reftest-wait.js"></script> + <script type="module" src="canvas_clear.js"></script> +</html>
diff --git a/webgpu/webgpu/web-platform/reftests/canvas_clear.js b/webgpu/webgpu/web-platform/reftests/canvas_clear.js new file mode 100644 index 0000000..52ffaed --- /dev/null +++ b/webgpu/webgpu/web-platform/reftests/canvas_clear.js
@@ -0,0 +1,31 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { runRefTest } from './gpu_ref_test.js'; +runRefTest(async t => { + const canvas = document.getElementById('gpucanvas'); + const ctx = canvas.getContext('gpupresent'); + const swapChain = ctx.configureSwapChain({ + device: t.device, + format: 'bgra8unorm' + }); + const colorAttachment = swapChain.getCurrentTexture(); + const colorAttachmentView = colorAttachment.createView(); + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [{ + attachment: colorAttachmentView, + loadValue: { + r: 0.0, + g: 1.0, + b: 0.0, + a: 1.0 + }, + storeOp: 'store' + }] + }); + pass.endPass(); + t.device.defaultQueue.submit([encoder.finish()]); +}); +//# sourceMappingURL=canvas_clear.js.map \ No newline at end of file diff --git a/webgpu/webgpu/web-platform/reftests/canvas_complex.js b/webgpu/webgpu/web-platform/reftests/canvas_complex.js new file mode 100644 index 0000000..1e63a0a --- /dev/null +++ b/webgpu/webgpu/web-platform/reftests/canvas_complex.js
@@ -0,0 +1,90 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { unreachable } from '../../../common/framework/util/util.js'; +import { runRefTest } from './gpu_ref_test.js'; // <canvas> element from html page + +export function run(format) { + runRefTest(async t => { + const ctx = cvs.getContext('gpupresent'); + + switch (format) { + case 'bgra8unorm': + case 'rgba16float': + break; + + default: + unreachable(); + } + + const swapChain = ctx.configureSwapChain({ + device: t.device, + format, + usage: GPUTextureUsage.COPY_DST + }); + const rows = 2; + const bytesPerRow = 256; + const [buffer, mapping] = t.device.createBufferMapped({ + size: rows * bytesPerRow, + usage: GPUBufferUsage.COPY_SRC + }); + + switch (format) { + case 'bgra8unorm': + { + const data = new Uint8Array(mapping); + data.set(new Uint8Array([0x00, 0x00, 0x7f, 0xff]), 0); // red + + data.set(new Uint8Array([0x00, 0x7f, 0x00, 0xff]), 4); // green + + data.set(new Uint8Array([0x7f, 0x00, 0x00, 0xff]), 256 + 0); // blue + + data.set(new Uint8Array([0x00, 0x7f, 0x7f, 0xff]), 256 + 4); // yellow + } + break; + + case 'rgba16float': + { + // Untested! + const zero = 0x0000; + const half = 0x3800; + const one = 0x3c00; + const data = new DataView(mapping); + data.setUint16(0x000, half, false); // red + + data.setUint16(0x002, zero, false); + data.setUint16(0x004, zero, false); + data.setUint16(0x008, one, false); + data.setUint16(0x010, zero, false); // green + + data.setUint16(0x020, half, false); + data.setUint16(0x040, zero, false); + data.setUint16(0x080, one, false); + data.setUint16(0x100, zero, false); // blue + + data.setUint16(0x102, zero, false); + data.setUint16(0x104, half, false); + data.setUint16(0x108, one, false); + data.setUint16(0x110, half, false); // yellow + + data.setUint16(0x120, half, false); + data.setUint16(0x140, zero, false); + data.setUint16(0x180, one, false); + } + break; + } + + buffer.unmap(); + const texture = swapChain.getCurrentTexture(); + const encoder = t.device.createCommandEncoder(); + encoder.copyBufferToTexture({ + buffer, + bytesPerRow + }, { + texture + }, [2, 2, 1]); + t.device.defaultQueue.submit([encoder.finish()]); + }); +} +//# sourceMappingURL=canvas_complex.js.map \ No newline at end of file diff --git a/webgpu/webgpu/web-platform/reftests/canvas_complex_bgra8unorm.html b/webgpu/webgpu/web-platform/reftests/canvas_complex_bgra8unorm.html new file mode 100644 index 0000000..1310543 --- /dev/null +++ b/webgpu/webgpu/web-platform/reftests/canvas_complex_bgra8unorm.html
@@ -0,0 +1,18 @@ +<html class="reftest-wait"> + <title>WebGPU canvas_complex_bgra8unorm</title> + <meta charset="utf-8" /> + <link rel="help" href="https://gpuweb.github.io/gpuweb/" /> + <meta + name="assert" + content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space" + /> + <link rel="match" href="./ref/canvas_complex-ref.html" /> + + <canvas id="cvs" width="2" height="2" style="width: 20px; height: 20px;"></canvas> + <script src="/common/reftest-wait.js"></script> + <script type="module"> + import { run } from './canvas_complex.js'; + run('bgra8unorm'); + // TODO: make a copy of this test for rgba16float + </script> +</html> diff --git a/webgpu/webgpu/web-platform/reftests/gpu_ref_test.js b/webgpu/webgpu/web-platform/reftests/gpu_ref_test.js new file mode 100644 index 0000000..f45f232 --- /dev/null +++ b/webgpu/webgpu/web-platform/reftests/gpu_ref_test.js
@@ -0,0 +1,17 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/ + +import { assert } from '../../../common/framework/util/util.js'; +export async function runRefTest(fn) { + assert(typeof navigator !== 'undefined' && navigator.gpu !== undefined, 'No WebGPU implementation found'); + const adapter = await navigator.gpu.requestAdapter(); + const device = await adapter.requestDevice(); + const queue = device.defaultQueue; + await fn({ + device, + queue + }); + takeScreenshotDelayed(50); +} +//# sourceMappingURL=gpu_ref_test.js.map \ No newline at end of file diff --git a/webgpu/webgpu/web-platform/reftests/ref/canvas_clear-ref.html b/webgpu/webgpu/web-platform/reftests/ref/canvas_clear-ref.html new file mode 100644 index 0000000..2e07811 --- /dev/null +++ b/webgpu/webgpu/web-platform/reftests/ref/canvas_clear-ref.html
@@ -0,0 +1,12 @@ +<html> + <title>WebGPU canvas_clear (ref)</title> + <meta charset="utf-8" /> + <link rel="help" href="https://gpuweb.github.io/gpuweb/" /> + <canvas id="myCanvas" width="10" height="10"></canvas> + <script> + var c = document.getElementById('myCanvas'); + var ctx = c.getContext('2d'); + ctx.fillStyle = '#00FF00'; + ctx.fillRect(0, 0, c.width, c.height); + </script> +</html> diff --git a/webgpu/webgpu/web-platform/reftests/ref/canvas_complex-ref.html b/webgpu/webgpu/web-platform/reftests/ref/canvas_complex-ref.html new file mode 100644 index 0000000..3d5b3b3 --- /dev/null +++ b/webgpu/webgpu/web-platform/reftests/ref/canvas_complex-ref.html
@@ -0,0 +1,17 @@ +<html> + <title>WebGPU canvas_complex (ref)</title> + <meta charset="utf-8" /> + <link rel="help" href="https://gpuweb.github.io/gpuweb/" /> + <canvas id="cvs" width="2" height="2" style="width: 20px; height: 20px;"></canvas> + <script> + const ctx = cvs.getContext('2d'); + ctx.fillStyle = '#7F0000'; + ctx.fillRect(0, 0, 1, 1); + ctx.fillStyle = '#007F00'; + ctx.fillRect(1, 0, 1, 1); + ctx.fillStyle = '#00007F'; + ctx.fillRect(0, 1, 1, 1); + ctx.fillStyle = '#7F7F00'; + ctx.fillRect(1, 1, 1, 1); + </script> +</html>